import { useEffect, useRef, useState } from 'react';
import { ArrowLeft, Check, TriangleAlert } from 'lucide-react';
import { Button, Card, Input, Spinner } from '../components';
import {
  getRemoteConnection,
  messageFromMediaSourcesError,
  saveRemoteConnection
} from '../api';
import { navigateToPath } from '../routing';
import { familyMeta, familyRoute } from '../mediaSources/familyMeta';
import { useDirtyGuard } from '../mediaSources/useDirtyGuard';

type RemoteFamilyWithConnection = 'jellyfin' | 'emby';

interface Draft {
  address: string;
  apiKey: string;
}

interface LoadedState {
  hasApiKey: boolean;
}

const DIRTY_PROMPT = 'You have unsaved connection changes. Discard them?';

// Absolute-URL validation matching RemoteMediaSourceEditViewModelValidator (non-empty absolute URI).
function isValidAddress(value: string): boolean {
  const trimmed = value.trim();
  if (!trimmed) {
    return false;
  }
  try {
    const url = new URL(trimmed);
    return url.protocol === 'http:' || url.protocol === 'https:';
  } catch {
    return false;
  }
}

// Shared connection editor for the api-key families (Jellyfin / Emby), mirroring
// RemoteMediaSourceEditor. SECURE KEY AFFORDANCE (design §C3 / finding 1): the stored api key NEVER
// crosses the wire — GET returns only { address, hasApiKey }. When a key is already set the field
// shows "leave blank to keep" and is optional (blank retains the existing key server-side); on first
// connect the key is required. The stored key is never displayed.
export function RemoteConnectionEditScreen({ family }: { family: RemoteFamilyWithConnection }) {
  const meta = familyMeta(family);
  const [loaded, setLoaded] = useState<LoadedState | null>(null);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [draft, setDraft] = useState<Draft>({ address: '', apiKey: '' });
  const [saved, setSaved] = useState<Draft>({ address: '', apiKey: '' });
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  const activeRef = useRef(true);

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

  useEffect(() => {
    getRemoteConnection(family)
      .then((connection) => {
        if (!activeRef.current) {
          return;
        }
        const initial: Draft = { address: connection.address ?? '', apiKey: '' };
        setDraft(initial);
        setSaved(initial);
        setLoaded({ hasApiKey: connection.hasApiKey });
      })
      .catch((error: unknown) => {
        if (activeRef.current) {
          setLoadError(messageFromMediaSourcesError(error, `Unable to load ${meta.label} connection`));
        }
      });
  }, [family, meta.label]);

  const hasApiKey = loaded?.hasApiKey ?? false;
  const addressValid = isValidAddress(draft.address);
  // First connect (no stored key) requires a key; when a key is already set, blank means "retain".
  const apiKeyValid = hasApiKey || draft.apiKey.trim().length > 0;
  const valid = addressValid && apiKeyValid;
  const dirty = JSON.stringify(draft) !== JSON.stringify(saved);

  const markDirtyGuardClean = useDirtyGuard(dirty, DIRTY_PROMPT);

  if (loadError) {
    return (
      <div className="ctv-settings-error" role="alert">
        <TriangleAlert aria-hidden="true" size={16} />
        <span>{loadError}</span>
        <Button onClick={() => navigateToPath(familyRoute(family))} size="sm" variant="secondary">Back to {meta.label}</Button>
      </div>
    );
  }

  if (loaded === null) {
    return (
      <div className="ctv-settings-loading">
        <Spinner size={16} tone="muted" />
        <span>Loading {meta.label} connection…</span>
      </div>
    );
  }

  const discard = () => {
    if (!dirty || window.confirm(DIRTY_PROMPT)) {
      setDraft(saved);
      setSaveError(null);
    }
  };

  const save = () => {
    if (!valid || !dirty || saving) {
      return;
    }
    setSaving(true);
    setSaveError(null);
    saveRemoteConnection(family, { address: draft.address.trim(), apiKey: draft.apiKey })
      .then((connection) => {
        if (!activeRef.current) {
          return;
        }
        // Reset the baseline from the reloaded connection; the key is intentionally not returned.
        const next: Draft = { address: connection.address ?? '', apiKey: '' };
        setDraft(next);
        setSaved(next);
        setLoaded({ hasApiKey: connection.hasApiKey });
        setSaving(false);
        markDirtyGuardClean();
        navigateToPath(familyRoute(family));
      })
      .catch((error: unknown) => {
        if (!activeRef.current) {
          return;
        }
        // Draft retained on 422 / network / 409 so the user's input is not lost.
        setSaving(false);
        setSaveError(messageFromMediaSourcesError(error, `Unable to save ${meta.label} connection`));
      });
  };

  return (
    <div className="ctv-libraries-screen">
      <section className="ctv-libraries-header">
        <div>
          <span className="ctv-kicker">Media source</span>
          <h2>{hasApiKey ? `Edit ${meta.label} connection` : `Connect ${meta.label}`}</h2>
          <p>Point ErsatzTV at your {meta.label} server and authenticate with an API key.</p>
        </div>
        <div className="ctv-libraries-header-actions">
          <Button onClick={() => navigateToPath(familyRoute(family))} startIcon={<ArrowLeft aria-hidden="true" size={14} />} variant="ghost">Back to {meta.label}</Button>
        </div>
      </section>

      <Card title={<h3>Connection</h3>} subtitle={`${meta.label} server address and API key.`}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 480 }}>
          <Input
            disabled={saving}
            label="Address"
            value={draft.address}
            onChange={(event) => {
              if (!saving) {
                setDraft((current) => ({ ...current, address: event.target.value }));
              }
            }}
            placeholder="http://192.168.1.50:8096"
            error={draft.address.trim() && !addressValid ? 'Enter a valid absolute http(s) URL' : null}
          />
          <Input
            disabled={saving}
            label="API key"
            type="password"
            value={draft.apiKey}
            onChange={(event) => {
              if (!saving) {
                setDraft((current) => ({ ...current, apiKey: event.target.value }));
              }
            }}
            placeholder={hasApiKey ? 'Key is set — leave blank to keep, or enter a new key' : 'Enter an API key'}
            error={!apiKeyValid && draft.apiKey.length === 0 && !hasApiKey ? 'API key is required' : null}
          />
          {hasApiKey && (
            <span className="ctv-settings-row-help">Key is set — leave blank to keep the existing key, or enter a new key to replace it.</span>
          )}
        </div>
      </Card>

      {saveError && (
        <div className="ctv-channels-error" role="alert">
          <TriangleAlert aria-hidden="true" size={15} />
          <span>{saveError}</span>
        </div>
      )}

      <div className="ctv-schedule-toolbar">
        <div className="ctv-schedule-toolbar-spacer" />
        <Button disabled={!dirty || saving} onClick={discard} variant="ghost">Discard</Button>
        <Button disabled={!valid || !dirty || saving} loading={saving} onClick={save} startIcon={<Check aria-hidden="true" size={14} />}>
          {hasApiKey ? 'Save connection' : 'Connect'}
        </Button>
      </div>
    </div>
  );
}
