import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { Check, Copy, Eye, EyeOff, KeyRound, Lock, ShieldCheck, TriangleAlert } from 'lucide-react';
import { Button, Card, Input, Spinner } from '../components';
import { ApiError, changePassword, getMachineKey } from '../api';
import { AuthContext } from '../AuthGate';

// Since #295 the browser authenticates with the session cookie, so this screen no longer bootstraps an
// API key. It now MANAGES the server's machine key (for MCP / external REST clients) and — for local
// admin accounts — offers a password change. It's a normal authenticated screen: it reads the machine
// key from `/api/v1/auth/machine-key` (a real `/api` call), unlike the old keyless localStorage-only shape.
type KeyState =
  | { status: 'loading' }
  | { status: 'error'; message: string }
  | { status: 'success'; key: string };

function messageFromError(error: unknown, fallback: string): string {
  if (error instanceof ApiError) {
    return error.detail ?? error.message;
  }
  if (error instanceof Error) {
    return error.message;
  }
  return fallback;
}

function CopyButton({ text }: { text: string }) {
  const [copied, setCopied] = useState(false);

  const copy = () => {
    void navigator.clipboard?.writeText(text).then(() => {
      setCopied(true);
      window.setTimeout(() => setCopied(false), 1500);
    });
  };

  return (
    <Button
      onClick={copy}
      size="sm"
      startIcon={copied ? <Check aria-hidden="true" size={14} /> : <Copy aria-hidden="true" size={14} />}
      variant="secondary"
    >
      {copied ? 'Copied' : 'Copy'}
    </Button>
  );
}

function MachineKeyCard() {
  const [state, setState] = useState<KeyState>({ status: 'loading' });
  const [revealed, setRevealed] = useState(false);
  const activeRef = useRef(true);
  const seqRef = useRef(0);

  useEffect(() => {
    activeRef.current = true;
    return () => {
      activeRef.current = false;
    };
  }, []);

  // State transitions live in the promise callbacks (never synchronously in the effect body) per
  // docs/spa-conventions.md §3.
  const load = useCallback(() => {
    const seq = ++seqRef.current;
    getMachineKey()
      .then((result) => {
        if (!activeRef.current || seq !== seqRef.current) {
          return;
        }
        setState({ status: 'success', key: result.apiKey });
      })
      .catch((error: unknown) => {
        if (!activeRef.current || seq !== seqRef.current) {
          return;
        }
        setState({ status: 'error', message: messageFromError(error, 'Unable to load the machine key') });
      });
  }, []);

  useEffect(() => {
    load();
  }, [load]);

  return (
    <Card
      padded
      subtitle="This key authenticates MCP and external REST clients. The browser no longer uses it — you sign in with your account instead."
      title="Machine API Key"
    >
      {state.status === 'loading' && (
        <div className="ctv-collections-loading" role="status">
          <Spinner size={16} />
          <span>Loading machine key…</span>
        </div>
      )}

      {state.status === 'error' && (
        <div className="ctv-channels-error" role="alert">
          <TriangleAlert aria-hidden="true" size={15} />
          <span>{state.message}</span>
          <span className="ctv-channels-spacer" />
          <Button
            onClick={() => {
              setState({ status: 'loading' });
              load();
            }}
            size="sm"
            variant="secondary"
          >
            Retry
          </Button>
        </div>
      )}

      {state.status === 'success' && (
        <>
          <div className="ctv-settings-warn-callout" role="status" style={{ borderColor: 'var(--status-ok)' }}>
            <ShieldCheck aria-hidden="true" color="var(--status-ok)" size={15} />
            <span>Give this key to an MCP server or external REST client to let it call this server's API.</span>
          </div>

          <div className="ctv-field ctv-field-full" style={{ marginTop: 16 }}>
            <span className="ctv-field-label">Machine key</span>
            <span className="ctv-field-frame" style={{ fontFamily: 'var(--font-mono)' }}>
              <span className="ctv-field-icon">
                <KeyRound aria-hidden="true" size={14} />
              </span>
              {/* Masked by default: the real key is never in the DOM until Reveal — Copy always copies
                  the real value regardless of the masked/revealed toggle. */}
              <code
                aria-label="Machine key"
                data-testid="machine-key"
                style={{ flex: 1, overflowX: 'auto', whiteSpace: 'nowrap' }}
              >
                {revealed ? state.key : '••••••••••••••••••••••••'}
              </code>
            </span>
          </div>

          <div className="ctv-channels-actionbar" style={{ marginTop: 16 }}>
            <span className="ctv-channels-spacer" />
            <Button
              onClick={() => setRevealed((current) => !current)}
              size="sm"
              startIcon={revealed ? <EyeOff aria-hidden="true" size={14} /> : <Eye aria-hidden="true" size={14} />}
              variant="ghost"
            >
              {revealed ? 'Hide' : 'Reveal'}
            </Button>
            <CopyButton text={state.key} />
          </div>
        </>
      )}
    </Card>
  );
}

function LocalPasswordCard() {
  const [currentPassword, setCurrentPassword] = useState('');
  const [newPassword, setNewPassword] = useState('');
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<null | string>(null);
  const [justSaved, setJustSaved] = useState(false);

  const canSubmit = currentPassword.length > 0 && newPassword.length > 0 && !saving;

  const submit = async () => {
    if (!canSubmit) {
      return;
    }
    setSaving(true);
    setError(null);
    setJustSaved(false);
    try {
      await changePassword(currentPassword, newPassword);
      setCurrentPassword('');
      setNewPassword('');
      setJustSaved(true);
    } catch (err: unknown) {
      // A 401 is the expected wrong-current-password answer — surface its ProblemDetails detail inline.
      if (err instanceof ApiError && err.status === 401) {
        setError(err.detail ?? 'Your current password is incorrect.');
      } else {
        setError(messageFromError(err, 'Unable to change your password'));
      }
    } finally {
      setSaving(false);
    }
  };

  return (
    <Card padded subtitle="Change the password for your local admin account." title="Local admin password">
      {error && (
        <div className="ctv-settings-warn-callout" role="alert">
          <TriangleAlert aria-hidden="true" color="var(--status-warn)" size={15} />
          <span>{error}</span>
        </div>
      )}

      <div style={{ marginTop: error ? 16 : 0 }}>
        <Input
          label="Current password"
          leadingIcon={<Lock aria-hidden="true" size={14} />}
          onChange={(event) => {
            setCurrentPassword(event.target.value);
            setJustSaved(false);
          }}
          type="password"
          value={currentPassword}
        />
      </div>

      <div style={{ marginTop: 12 }}>
        <Input
          label="New password"
          leadingIcon={<Lock aria-hidden="true" size={14} />}
          onChange={(event) => {
            setNewPassword(event.target.value);
            setJustSaved(false);
          }}
          type="password"
          value={newPassword}
        />
      </div>

      <div className="ctv-channels-actionbar" style={{ marginTop: 16 }}>
        {justSaved && (
          <span style={{ alignItems: 'center', color: 'var(--status-ok)', display: 'inline-flex', gap: 6 }}>
            <Check aria-hidden="true" size={14} />
            <span>Password changed.</span>
          </span>
        )}
        <span className="ctv-channels-spacer" />
        <Button
          disabled={!canSubmit}
          onClick={() => void submit()}
          startIcon={<Check aria-hidden="true" size={14} />}
          variant="primary"
        >
          {saving ? 'Changing…' : 'Change password'}
        </Button>
      </div>
    </Card>
  );
}

export function ApiKeyScreen() {
  const { method } = useContext(AuthContext);

  return (
    <div className="ctv-collections" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <MachineKeyCard />
      {method === 'local' && <LocalPasswordCard />}
    </div>
  );
}
