import type { JellyfinServerRow } from './useJellyfinData';
import { useState } from 'react';
import { Check, CheckCircle2, Loader2, Plug, Trash2, TriangleAlert } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { formatShortDate } from './shared';
import {
  jellyfinErrorMessage,
  useJellyfinHealth,
  useJellyfinServerActions,
  useJellyfinServers,
} from './useJellyfinData';

// Connecting Jellyfin servers to Officer, from the app.
//
// This screen is BOTH the setup wizard and the permanent settings page: JellyfinView renders it in place of
// whatever section the URL asks for while nothing is connected, and /jellyfin/settings renders it for good.
// One component, so a second server added later goes through exactly the code path that stored the first.
//
// There is no "paste an API key" alternative here, unlike the Immich screen. Jellyfin's API keys are
// SERVER-wide and identity-less — they authenticate as nobody, which breaks every per-user route this app
// depends on (resume points, next up, watched marks). Signing in is the only credential that has a user
// attached, so it is the only one offered. The password is used once and never stored.

const HINT = 'text-[11px] leading-relaxed text-muted-foreground';

/** Jellyfin's own default port. The sidecar dials from this machine, so localhost is the right default. */
const DEFAULT_URL = 'http://localhost:8096';

const URL_HINT =
  "The server's base URL, without /web. Officer reaches it from the server, not from this browser — so " +
  'localhost here means the machine Officer runs on, and a local instance needs no TLS.';

const SIGN_IN_HINT =
  'Your Jellyfin login. It is used once, by the sidecar, to exchange for an access token — the password is ' +
  'never stored and never reaches Postgres. The session shows up in Jellyfin under Devices as "Officer"; ' +
  'signing it out there is what revokes this connection.';

type FieldProps = {
  label: string;
  hint?: string;
  value: string;
  onChange: (value: string) => void;
  placeholder: string;
  type?: string;
  autoFocus?: boolean;
};

const Field = ({ label, hint, value, onChange, placeholder, type, autoFocus }: FieldProps) => (
  <label className="flex flex-col gap-1.5">
    <span className="text-xs font-medium">{label}</span>
    <Input
      value={value}
      onChange={(ev) => onChange(ev.target.value)}
      placeholder={placeholder}
      type={type}
      autoFocus={autoFocus}
      autoComplete="off"
      spellCheck={false}
    />
    {hint && <span className={HINT}>{hint}</span>}
  </label>
);

function useCredential() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const filled = !!username.trim() && !!password;
  const reset = () => {
    setUsername('');
    setPassword('');
  };

  return { username, setUsername, password, setPassword, filled, reset };
}

type CredentialState = ReturnType<typeof useCredential>;

const CredentialFields = ({ cred, autoFocus }: { cred: CredentialState; autoFocus?: boolean }) => (
  <>
    <Field
      label="Username"
      value={cred.username}
      onChange={cred.setUsername}
      placeholder="your Jellyfin user"
      autoFocus={autoFocus}
    />
    <Field
      label="Password"
      value={cred.password}
      onChange={cred.setPassword}
      placeholder="••••••••"
      type="password"
      hint={SIGN_IN_HINT}
    />
  </>
);

/**
 * One stored server. The active one carries the live health line, because health only ever describes the
 * server actually in use — showing a status next to the others would be inventing one. "Test" on an inactive
 * row probes it directly instead, which is the only way to check a server without switching to it first.
 */
const ServerRow = ({ server }: { server: JellyfinServerRow }) => {
  const { data: health, refetch: recheck, isFetching: checking } = useJellyfinHealth();
  const { edit, activate, remove, test } = useJellyfinServerActions();

  const cred = useCredential();
  const [error, setError] = useState<string | null>(null);
  const [replacing, setReplacing] = useState(false);
  // Removing a server throws away a token Officer can never show again, so the bin asks once.
  const [confirming, setConfirming] = useState(false);

  const run = async (action: Promise<unknown>) => {
    setError(null);
    try {
      await action;
      cred.reset();
      setReplacing(false);
    } catch (err) {
      setError(jellyfinErrorMessage(err));
    }
  };

  const busy = checking || test.isPending;

  return (
    <div className={`flex flex-col gap-2 rounded-lg border p-4 text-xs ${server.isActive ? 'border-primary/40' : ''}`}>
      <div className="flex items-center gap-2">
        {server.isActive ? <Check className="h-4 w-4 shrink-0 text-primary" /> : <span className="h-4 w-4 shrink-0" />}
        <span className="truncate font-medium">{server.label}</span>
        {server.isActive && (
          <span className="rounded bg-primary/10 px-1.5 py-0.5 text-[10px] text-primary">in use</span>
        )}
        <div className="ml-auto flex items-center gap-1">
          {!server.isActive && (
            <Button
              variant="ghost"
              size="sm"
              className="h-7"
              disabled={activate.isPending}
              onClick={() => void run(activate.mutateAsync(server.id))}
            >
              Use
            </Button>
          )}
          <Button
            variant="ghost"
            size="sm"
            className="h-7"
            disabled={busy}
            onClick={() => void (server.isActive ? recheck() : run(test.mutateAsync(server.id)))}
          >
            {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : 'Test'}
          </Button>
          <Button
            variant="ghost"
            size="sm"
            className={`h-7 ${confirming ? 'text-destructive' : 'text-muted-foreground hover:text-destructive'}`}
            disabled={remove.isPending}
            onClick={() => {
              if (!confirming) return setConfirming(true);
              setConfirming(false);
              void run(remove.mutateAsync(server.id));
            }}
          >
            {confirming ? 'Remove?' : <Trash2 className="h-3.5 w-3.5" />}
          </Button>
        </div>
      </div>

      <p className="truncate text-muted-foreground">
        {server.url}
        {server.jellyfinUsername ? ` · ${server.jellyfinUsername}` : ''}
      </p>

      {server.isActive ? (
        <div className="flex items-center gap-1.5 text-muted-foreground">
          {health?.ok ? (
            <CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
          ) : (
            <TriangleAlert className="h-3.5 w-3.5 text-amber-500" />
          )}
          <span>
            {health?.ok
              ? `Jellyfin ${health.version ?? '?'}${health.serverName ? ` · ${health.serverName}` : ''}`
              : (health?.error ?? 'not checked yet')}
          </span>
        </div>
      ) : (
        <p className="text-muted-foreground">
          {server.version ? `Jellyfin ${server.version}` : 'version unknown'}
          {server.lastSeenAt ? ` · last answered ${formatShortDate(server.lastSeenAt)}` : ''}
        </p>
      )}

      {replacing ? (
        <div className="flex flex-col gap-3 rounded-lg border p-3">
          <CredentialFields cred={cred} autoFocus />
          <div className="flex items-center gap-2">
            <Button
              size="sm"
              className="h-8"
              disabled={!cred.filled || edit.isPending}
              onClick={() =>
                void run(edit.mutateAsync({ id: server.id, username: cred.username.trim(), password: cred.password }))
              }
            >
              {edit.isPending && <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />}
              Save
            </Button>
            <Button
              variant="ghost"
              size="sm"
              className="h-8"
              onClick={() => {
                cred.reset();
                setReplacing(false);
              }}
            >
              Cancel
            </Button>
          </div>
        </div>
      ) : (
        <button
          type="button"
          onClick={() => setReplacing(true)}
          className="self-start text-[11px] text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
        >
          Sign in again
        </button>
      )}

      {error && <p className="text-destructive">{error}</p>}
    </div>
  );
};

export const ConnectionSection = () => {
  const { data, isLoading } = useJellyfinServers();
  const { add } = useJellyfinServerActions();

  const cred = useCredential();
  const [label, setLabel] = useState('');
  const [url, setUrl] = useState(DEFAULT_URL);
  const [error, setError] = useState<string | null>(null);

  const servers = data?.servers ?? [];
  const hasServers = servers.length > 0;

  const submit = async () => {
    setError(null);
    if (!url.trim()) return setError('The Jellyfin URL is required');
    if (!cred.filled) return setError('A username and password are required');

    try {
      await add.mutateAsync({
        label: label.trim(),
        url: url.trim(),
        username: cred.username.trim(),
        password: cred.password,
      });
      setLabel('');
      setUrl(DEFAULT_URL);
      cred.reset();
    } catch (err) {
      setError(jellyfinErrorMessage(err));
    }
  };

  return (
    <div className="h-full overflow-y-auto">
      <div className="mx-auto flex max-w-xl flex-col gap-4 p-6">
        <div className="flex items-center gap-3">
          <div className="flex h-9 w-9 items-center justify-center rounded-xl bg-violet-500/15 text-violet-400">
            <Plug className="h-5 w-5" />
          </div>
          <div>
            <h2 className="text-sm font-semibold">{hasServers ? 'Jellyfin servers' : 'Connect Jellyfin'}</h2>
            <p className="text-xs text-muted-foreground">
              {hasServers
                ? 'Officer stores each server and its access token encrypted, for this account only. One is in use at a time.'
                : 'Video needs a Jellyfin server and a login before it can show anything.'}
            </p>
          </div>
        </div>

        {!isLoading && servers.map((server) => <ServerRow key={server.id} server={server} />)}

        <form
          onSubmit={(ev) => {
            ev.preventDefault();
            void submit();
          }}
          className="flex flex-col gap-3 rounded-lg border p-4"
        >
          <p className="text-xs font-medium">{hasServers ? 'Add another server' : 'Add a server'}</p>
          <Field
            label="Label"
            value={label}
            onChange={setLabel}
            placeholder="Optional — defaults to the server's own name"
            hint="What the switcher calls this server. Two servers cannot share a label; two can share a URL."
            autoFocus={hasServers}
          />
          <Field
            label="Jellyfin URL"
            value={url}
            onChange={setUrl}
            placeholder={DEFAULT_URL}
            hint={URL_HINT}
            autoFocus={!hasServers}
          />
          <CredentialFields cred={cred} />

          {error && <p className="text-xs text-destructive">{error}</p>}

          <div className="flex items-center gap-2">
            <Button type="submit" size="sm" disabled={add.isPending}>
              {add.isPending && <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />}
              {add.isPending ? 'Verifying…' : hasServers ? 'Add server' : 'Connect'}
            </Button>
            {add.isPending && <span className={HINT}>Checking the server and signing in…</span>}
          </div>
        </form>
      </div>
    </div>
  );
};
