import type { PhotosAccount, PhotosCredential } from './usePhotosData';
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 { photosErrorMessage, usePhotosAccountActions, usePhotosAccounts, usePhotosHealth } from './usePhotosData';

// Connecting Immich libraries to Officer, from the app.
//
// This screen is BOTH the setup wizard and the permanent settings page: PhotosView renders it in place of
// whatever section the URL asks for while nothing is connected, and /photos/settings renders it for good.
// One component, so a second account added later goes through exactly the code path that stored the first.
//
// It is a registry, not a single connection — two keys against the same instance (one per Immich user) is
// the ordinary case, which is why each account carries a LABEL and why the label, not the URL, is what has
// to be unique. The API key is never displayed, because Officer cannot display it: it is encrypted at rest
// and the sidecar's GET has no field that could carry it back.

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

/** Immich's own default — its compose file publishes 2283, and the sidecar dials from this machine. */
const DEFAULT_URL = 'http://localhost:2283';

const URL_HINT =
  "The instance's base URL, without /api. 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 KEY_HINT =
  'Immich → Account Settings → API Keys. Grant all permissions unless you have a reason not to: Immich keys ' +
  'are scoped, and a partial key looks like a broken feature rather than a rejected credential.';

const SIGN_IN_HINT =
  'Your Immich login. It is used once, by the sidecar, to mint an all-permissions API key — the password is ' +
  'never stored and the sign-in session is closed straight after, so no phantom device is left in Immich. ' +
  'The key appears in Immich → Account Settings → API Keys as "Officer"; revoke it there.';

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>
);

// ── credentials ──────────────────────────────────────────────────────────────────────────────────
//
// Two ways to prove the same thing, so they share one piece of state rather than two forms. Sign-in is the
// default: it is the only path that does not require the owner to go and read an API key out of Immich's own
// settings first, and what it stores is the same kind of key they would have pasted.

type CredentialMode = 'login' | 'key';

function useCredential() {
  const [mode, setMode] = useState<CredentialMode>('login');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [apiKey, setApiKey] = useState('');

  const filled = mode === 'key' ? !!apiKey.trim() : !!email.trim() && !!password;
  const payload: PhotosCredential = mode === 'key' ? { apiKey: apiKey.trim() } : { email: email.trim(), password };

  const reset = () => {
    setEmail('');
    setPassword('');
    setApiKey('');
  };

  return { mode, setMode, email, setEmail, password, setPassword, apiKey, setApiKey, filled, payload, reset };
}

type CredentialState = ReturnType<typeof useCredential>;

const TAB = 'rounded-md px-2 py-1 text-[11px] transition-colors';

const CredentialFields = ({ cred, autoFocus }: { cred: CredentialState; autoFocus?: boolean }) => (
  <div className="flex flex-col gap-3">
    <div className="flex items-center gap-1">
      <span className="text-xs font-medium">Credential</span>
      <div className="ml-auto flex items-center gap-1 rounded-lg bg-muted p-0.5">
        <button
          type="button"
          onClick={() => cred.setMode('login')}
          className={`${TAB} ${cred.mode === 'login' ? 'bg-background shadow-sm' : 'text-muted-foreground'}`}
        >
          Sign in
        </button>
        <button
          type="button"
          onClick={() => cred.setMode('key')}
          className={`${TAB} ${cred.mode === 'key' ? 'bg-background shadow-sm' : 'text-muted-foreground'}`}
        >
          API key
        </button>
      </div>
    </div>

    {cred.mode === 'login' ? (
      <>
        <Field
          label="Email"
          value={cred.email}
          onChange={cred.setEmail}
          placeholder="you@example.com"
          type="email"
          autoFocus={autoFocus}
        />
        <Field
          label="Password"
          value={cred.password}
          onChange={cred.setPassword}
          placeholder="••••••••"
          type="password"
          hint={SIGN_IN_HINT}
        />
      </>
    ) : (
      <Field
        label="API key"
        value={cred.apiKey}
        onChange={cred.setApiKey}
        placeholder="••••••••••••"
        type="password"
        hint={KEY_HINT}
        autoFocus={autoFocus}
      />
    )}
  </div>
);

type AccountRowProps = { account: PhotosAccount };

/**
 * One stored account. The active one carries the live health line, because health only ever describes the
 * account actually being used — showing a status next to the others would be inventing one.
 */
const AccountRow = ({ account }: AccountRowProps) => {
  const { data: health, refetch: recheck, isFetching: checking } = usePhotosHealth();
  const { edit, activate, remove } = usePhotosAccountActions();

  const cred = useCredential();
  const [error, setError] = useState<string | null>(null);
  const [replacing, setReplacing] = useState(false);
  // Removing an account throws away a key 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(photosErrorMessage(err));
    }
  };

  return (
    <div className={`flex flex-col gap-2 rounded-lg border p-4 text-xs ${account.isActive ? 'border-primary/40' : ''}`}>
      <div className="flex items-center gap-2">
        {account.isActive ? <Check className="h-4 w-4 shrink-0 text-primary" /> : <span className="h-4 w-4 shrink-0" />}
        <span className="truncate font-medium">{account.label}</span>
        {account.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">
          {!account.isActive && (
            <Button
              variant="ghost"
              size="sm"
              className="h-7"
              disabled={activate.isPending}
              onClick={() => void run(activate.mutateAsync(account.id))}
            >
              Use
            </Button>
          )}
          {account.isActive && (
            <Button variant="ghost" size="sm" className="h-7" onClick={() => void recheck()}>
              {checking ? <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(account.id));
            }}
          >
            {confirming ? 'Remove?' : <Trash2 className="h-3.5 w-3.5" />}
          </Button>
        </div>
      </div>

      <p className="truncate text-muted-foreground">{account.url}</p>

      {account.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
              ? `Immich ${health.version ?? '?'}${health.user ? ` · ${health.user}` : ''}`
              : (health?.error ?? 'not checked yet')}
          </span>
        </div>
      ) : (
        <p className="text-muted-foreground">
          {account.version ? `Immich ${account.version}` : 'version unknown'}
          {account.lastSeenAt ? ` · last answered ${formatShortDate(account.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: account.id, ...cred.payload }))}
            >
              {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"
        >
          Replace credential
        </button>
      )}

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

export const ConnectionSection = () => {
  const { data, isLoading } = usePhotosAccounts();
  const { add } = usePhotosAccountActions();

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

  const accounts = data?.accounts ?? [];
  const hasAccounts = accounts.length > 0;

  const submit = async () => {
    setError(null);
    if (!url.trim()) return setError('The Immich URL is required');
    if (!cred.filled) {
      return setError(cred.mode === 'key' ? 'An API key is required' : 'An email and password are required');
    }

    try {
      await add.mutateAsync({ label: label.trim(), url: url.trim(), ...cred.payload });
      setLabel('');
      setUrl(DEFAULT_URL);
      cred.reset();
    } catch (err) {
      setError(photosErrorMessage(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-emerald-500/15 text-emerald-400">
            <Plug className="h-5 w-5" />
          </div>
          <div>
            <h2 className="text-sm font-semibold">{hasAccounts ? 'Immich accounts' : 'Connect Immich'}</h2>
            <p className="text-xs text-muted-foreground">
              {hasAccounts
                ? 'Officer stores each instance and its key encrypted, for this account only. One is in use at a time.'
                : 'Photos needs an Immich instance and a login before it can show anything.'}
            </p>
          </div>
        </div>

        {!isLoading && accounts.map((account) => <AccountRow key={account.id} account={account} />)}

        <form
          onSubmit={(ev) => {
            ev.preventDefault();
            void submit();
          }}
          className="flex flex-col gap-3 rounded-lg border p-4"
        >
          <p className="text-xs font-medium">{hasAccounts ? 'Add another account' : 'Add an account'}</p>
          <Field
            label="Label"
            value={label}
            onChange={setLabel}
            placeholder="Optional — defaults to the Immich user"
            hint="What the switcher calls this account. Two accounts cannot share a label; two can share a URL."
            autoFocus={hasAccounts}
          />
          <Field
            label="Immich URL"
            value={url}
            onChange={setUrl}
            placeholder={DEFAULT_URL}
            hint={URL_HINT}
            autoFocus={!hasAccounts}
          />
          <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…' : hasAccounts ? 'Add account' : 'Connect'}
            </Button>
            {add.isPending && <span className={HINT}>Checking the instance and the key…</span>}
          </div>
        </form>
      </div>
    </div>
  );
};
