"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import { api } from "@/lib/api";
import { Button, Card } from "@/components/ui";
import { openConsole, type ConsoleFailure, type ConsoleState } from "@/lib/guacConsole";

// The session's own lifecycle, which is not the same as the tunnel's: "idle"
// means the form is showing, and everything else means a session has been
// started and the viewport owns the page.
type SessionPhase = "idle" | ConsoleState;

const PHASE_LABEL: Record<Exclude<SessionPhase, "idle">, string> = {
    connecting: "Connecting",
    connected: "Connected",
    disconnected: "Disconnected",
    error: "Failed",
};

// Shape as well as colour: state must never read by colour alone.
const PHASE_DOT: Record<Exclude<SessionPhase, "idle">, string> = {
    connecting: "bg-warning animate-pulse",
    connected: "bg-success",
    disconnected: "bg-text-tertiary",
    error: "bg-danger",
};

export default function ServerConsolePage() {
    const params = useParams();
    const router = useRouter();
    const searchParams = useSearchParams();
    const serverId = params.id as string;

    const containerRef = useRef<HTMLDivElement>(null);
    const connectionRef = useRef<{
        disconnect: () => void;
        setScale: (scale: number) => void;
        resize: (width: number, height: number) => void;
        focus: () => void;
    } | null>(null);

    const [protocol, setProtocol] = useState<string>(searchParams.get("protocol") || "");
    const [keyId, setKeyId] = useState<string>("");
    const [sshUsername, setSshUsername] = useState<string>("root");
    const [rdpUsername, setRdpUsername] = useState("");
    const [rdpPassword, setRdpPassword] = useState("");
    const [vncPassword, setVncPassword] = useState("");
    const [connecting, setConnecting] = useState(false);
    const [phase, setPhase] = useState<SessionPhase>("idle");
    const [error, setError] = useState<string | null>(null);
    const [failure, setFailure] = useState<ConsoleFailure | null>(null);
    const connected = phase !== "idle";
    const [pending, setPending] = useState<{ token: string; wsPath: string } | null>(null);
    const [zoom, setZoom] = useState(1);
    const dprRef = useRef(1);

    
    useEffect(() => {
        const s = document.createElement("script");
        s.src = "/lib/guacamole-common.js";
        s.async = true;
        document.body.appendChild(s);
        return () => {
            document.body.removeChild(s);
        };
    }, []);

    
    useEffect(() => {
        return () => {
            connectionRef.current?.disconnect();
        };
    }, []);

    const { data: server, isLoading: serverLoading } = useQuery({
        queryKey: ["servers", serverId],
        queryFn: () => api.getServer(serverId),
    });

    const { data: keys, isLoading: keysLoading } = useQuery({
        queryKey: ["keys"],
        queryFn: () => api.listKeys(),
    });

    const usableKeys = useMemo(() => (keys ?? []).filter((k) => k.has_private_key === true), [keys]);
    const protocols = server?.console_protocols ?? [];

    useEffect(() => {
        if (!protocol && protocols.length > 0) {
            setProtocol(protocols[0]);
        }
    }, [protocols, protocol]);

    async function handleConnect() {
        setError(null);
        setFailure(null);
        setConnecting(true);
        try {
            const body: Parameters<typeof api.connectConsole>[0] = {
                server_id: serverId,
                protocol,
            };
            if (protocol === "ssh") {
                body.key_id = keyId || undefined;
                body.ssh_username = sshUsername || undefined;
            } else if (protocol === "rdp") {
                body.rdp_username = rdpUsername || undefined;
                body.rdp_password = rdpPassword || undefined;
            } else if (protocol === "vnc") {
                body.rdp_password = vncPassword || undefined;
            }

            const { token, ws_path } = await api.connectConsole(body);
            
            
            // "connecting", not "connected": all we have so far is a token. The
            // real state now comes from the tunnel, which is the only thing that
            // knows whether the far end ever answered.
            setPending({ token, wsPath: ws_path });
            setPhase("connecting");
        } catch (e) {
            const message = e instanceof Error ? e.message : "Failed to connect";
            setError(
                message.includes("agent_offline")
                    ? "The agent on this server is not connected, so a console session cannot be opened."
                    : message
            );
        } finally {
            setConnecting(false);
        }
    }

    
    
    useEffect(() => {
        if (!connected || !pending || !containerRef.current) return;

        const wsProto = location.protocol === "https:" ? "wss" : "ws";
        const wsUrl = `${wsProto}://${location.host}${pending.wsPath}`;
        const rect = containerRef.current.getBoundingClientRect();
        const dpr = window.devicePixelRatio || 1;
        dprRef.current = dpr;
        
        
        
        const connectData =
            `token=${encodeURIComponent(pending.token)}` +
            `&width=${Math.floor(rect.width * dpr)}` +
            `&height=${Math.floor(rect.height * dpr)}` +
            `&dpi=96`;

        connectionRef.current = openConsole(containerRef.current, wsUrl, connectData, {
            onState: (s) => setPhase(s),
            onFailure: (f) => setFailure(f),
        });
        connectionRef.current.setScale(zoom / dpr);
        setPending(null);
    }, [connected, pending]);

    
    
    
    
    useEffect(() => {
        if (!connectionRef.current || !containerRef.current) return;
        const rect = containerRef.current.getBoundingClientRect();
        const dpr = dprRef.current;
        const remoteW = Math.floor((rect.width * dpr) / zoom);
        const remoteH = Math.floor((rect.height * dpr) / zoom);
        connectionRef.current.resize(remoteW, remoteH);
        connectionRef.current.setScale(zoom / dpr);
    }, [zoom]);

    // Returns to the connection form. Used both by the Disconnect button and by
    // Reconnect, which is the same teardown followed by a fresh dial.
    function handleDisconnect() {
        connectionRef.current?.disconnect();
        connectionRef.current = null;
        setPhase("idle");
        setFailure(null);
        if (containerRef.current) {
            containerRef.current.innerHTML = "";
        }
    }

    function handleReconnect() {
        handleDisconnect();
        void handleConnect();
    }

    if (serverLoading || keysLoading) {
        return (
            <div className="flex h-full items-center justify-center">
                <div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-accent" />
            </div>
        );
    }

    if (!server) {
        return (
            <div className="p-4 sm:p-6 lg:p-8">
                <div className="rounded-lg border border-danger/30 bg-danger/10 p-4 text-danger">Server not found or failed to load.</div>
            </div>
        );
    }

    return (
        <div className="flex h-full flex-col p-4 sm:p-6 lg:p-8">
            <div className="mb-4 flex items-center gap-3">
                <Link href={`/servers/${serverId}`} className="text-text-secondary hover:text-text-primary text-sm">
                    ← {server.hostname}
                </Link>
            </div>

            <h1 className="mb-4 text-2xl font-bold text-text-primary">Console</h1>

            {error && <div className="mb-4 rounded-lg border border-danger/30 bg-danger/10 p-4 text-danger">{error}</div>}

            {!connected ? (
                <Card className="mb-4 max-w-xl">
                    <div className="space-y-4">
                        <div>
                            <label className="mb-1.5 block text-sm font-medium text-text-secondary">Protocol</label>
                            <select
                                value={protocol}
                                onChange={(e) => setProtocol(e.target.value)}
                                className="w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
                            >
                                {protocols.length === 0 && <option value="">No protocols available</option>}
                                {protocols.map((p) => (
                                    <option key={p} value={p}>
                                        {p.toUpperCase()}
                                    </option>
                                ))}
                            </select>
                        </div>

                        {protocol === "ssh" && (
                            <div>
                                <label className="mb-1.5 block text-sm font-medium text-text-secondary">SSH Username</label>
                                <input
                                    type="text"
                                    value={sshUsername}
                                    onChange={(e) => setSshUsername(e.target.value)}
                                    placeholder="root"
                                    className="mb-3 w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
                                />
                                <label className="mb-1.5 block text-sm font-medium text-text-secondary">SSH Key</label>
                                <select
                                    value={keyId}
                                    onChange={(e) => setKeyId(e.target.value)}
                                    className="w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
                                >
                                    <option value="">Select a key…</option>
                                    {usableKeys.map((k) => (
                                        <option key={k.key_id} value={k.key_id}>
                                            {k.label}
                                        </option>
                                    ))}
                                </select>
                                {usableKeys.length === 0 && (
                                    <p className="mt-1.5 text-xs text-text-tertiary">No keys with stored private material are available.</p>
                                )}
                            </div>
                        )}

                        {protocol === "rdp" && (
                            <>
                                <div>
                                    <label className="mb-1.5 block text-sm font-medium text-text-secondary">Username</label>
                                    <input
                                        type="text"
                                        value={rdpUsername}
                                        onChange={(e) => setRdpUsername(e.target.value)}
                                        className="w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
                                    />
                                </div>
                                <div>
                                    <label className="mb-1.5 block text-sm font-medium text-text-secondary">Password</label>
                                    <input
                                        type="password"
                                        value={rdpPassword}
                                        onChange={(e) => setRdpPassword(e.target.value)}
                                        autoComplete="new-password"
                                        className="w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
                                    />
                                </div>
                            </>
                        )}

                        {protocol === "vnc" && (
                            <div>
                                <label className="mb-1.5 block text-sm font-medium text-text-secondary">Password</label>
                                <input
                                    type="password"
                                    value={vncPassword}
                                    onChange={(e) => setVncPassword(e.target.value)}
                                    autoComplete="new-password"
                                    className="w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
                                />
                            </div>
                        )}

                        <Button variant="primary" loading={connecting} disabled={!protocol} onClick={handleConnect}>
                            Connect
                        </Button>
                    </div>
                </Card>
            ) : (
                <div className="mb-4 flex flex-wrap items-center gap-3">
                    <span className="inline-flex items-center gap-2 rounded-full border border-border bg-surface-2 px-3 py-1.5 text-xs font-medium text-text-secondary">
                        <span className={`h-2 w-2 rounded-full ${PHASE_DOT[phase as Exclude<SessionPhase, "idle">]}`} />
                        {PHASE_LABEL[phase as Exclude<SessionPhase, "idle">]}
                    </span>
                    <Button variant="danger" onClick={handleDisconnect}>
                        Disconnect
                    </Button>
                    {(phase === "error" || phase === "disconnected") && (
                        <Button variant="secondary" onClick={handleReconnect}>
                            Reconnect
                        </Button>
                    )}
                    <label className="text-sm text-text-secondary">Scale</label>
                    <select
                        value={zoom}
                        onChange={(e) => {
                            setZoom(Number(e.target.value));
                            e.target.blur();
                            connectionRef.current?.focus();
                        }}
                        className="rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary focus:border-accent/50 focus:outline-none focus:ring-1 focus:ring-accent/30"
                    >
                        <option value={0.5}>50%</option>
                        <option value={0.75}>75%</option>
                        <option value={1}>100%</option>
                        <option value={1.25}>125%</option>
                        <option value={1.5}>150%</option>
                        <option value={2}>200%</option>
                    </select>
                </div>
            )}

            {/* The viewport is always mounted — Guacamole attaches its display
                element to it on connect, so it cannot be conditionally rendered.
                Anything the operator needs to be told is layered over it
                instead, which is what a bare black rectangle never did. */}
            <div className="relative min-h-[500px] flex-1">
                <div
                    ref={containerRef}
                    className="absolute inset-0 overflow-hidden rounded-lg border border-border bg-well"
                />

                {phase === "connecting" && (
                    <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-3 rounded-lg bg-ground/70">
                        <div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-accent" />
                        <p className="text-sm text-text-secondary">
                            Opening {protocol.toUpperCase()} session on {server.hostname}…
                        </p>
                    </div>
                )}

                {(phase === "error" || phase === "disconnected") && (
                    <div className="absolute inset-0 flex items-center justify-center rounded-lg bg-ground/80 p-6">
                        <Card className="max-w-md">
                            <div className="space-y-3">
                                <div className="flex items-center gap-2">
                                    <span
                                        className={`h-2 w-2 rounded-full ${
                                            phase === "error" ? "bg-danger" : "bg-text-tertiary"
                                        }`}
                                    />
                                    <h2 className="text-sm font-semibold text-text-primary">
                                        {phase === "error" ? "Console session failed" : "Console session ended"}
                                    </h2>
                                </div>

                                <p className="text-sm text-text-secondary">
                                    {failure?.message ??
                                        (phase === "error"
                                            ? "The session ended without reporting a reason."
                                            : "The remote host closed the session.")}
                                </p>

                                {/* The numeric status is what makes a support
                                    ticket actionable, so it is shown rather than
                                    folded into the sentence above. */}
                                {failure?.code !== undefined && (
                                    <p className="font-mono text-xs text-text-tertiary">
                                        {failure.source === "tunnel" ? "tunnel" : "session"} status {failure.code}
                                    </p>
                                )}

                                <div className="flex gap-2 pt-1">
                                    <Button variant="primary" onClick={handleReconnect}>
                                        Reconnect
                                    </Button>
                                    <Button variant="secondary" onClick={handleDisconnect}>
                                        Change settings
                                    </Button>
                                </div>
                            </div>
                        </Card>
                    </div>
                )}
            </div>
        </div>
    );
}
