import type { ServiceConnectionState } from '../../hooks/useServiceConnection';
import { useEffect, useState } from 'react';
import { CheckCircle2, Loader2, RotateCcw, TriangleAlert } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
  serviceErrorMessage,
  useServiceConnection,
  useServiceConnectionActions,
} from '../../hooks/useServiceConnection';

// Where on-chain wallets read the chain from. One endpoint serves every on-chain wallet the owner has,
// which is why it sits here and not on a wallet.
//
// PRIVACY, NOT CUSTODY. A chain endpoint learns every address you ask it about, so pointing this at your
// own node is a real upgrade — but it never sees a key and cannot move a coin. That framing is on the
// card deliberately: the risk of getting it wrong is being watched, not being robbed.
//
// TWO PROTOCOLS, AND THEY ARE NOT INTERCHANGEABLE. Esplora is the public-explorer API (electrs, esplora,
// mempool.space); NBXplorer is what you run in front of your own bitcoind. The picker is not cosmetic —
// it selects a different client, so a URL saved under the wrong one fails the probe rather than silently
// misbehaving. bitcoin-core's own RPC port is neither.

type ChainKind = 'esplora' | 'nbxplorer';

type ChainState = ServiceConnectionState & {
  kind: ChainKind;
  esploraUrl: string;
  custom: boolean;
  defaultUrl: string;
  network: string;
};

type SaveResult = ChainState & { probe?: { blockHeight?: number | null; ms?: number } };

type KindMeta = { label: string; placeholder: string; hint: string };

const KINDS: Record<ChainKind, KindMeta> = {
  esplora: {
    label: 'Esplora',
    placeholder: 'https://mempool.space/api',
    hint: 'An Esplora REST API — electrs, esplora, or a mempool.space instance.',
  },
  nbxplorer: {
    label: 'NBXplorer',
    placeholder: 'http://127.0.0.1:32838',
    hint: 'NBXplorer in front of your own bitcoind. Its API root, not bitcoind’s RPC port.',
  },
};

export const ChainSourceCard = () => {
  const { data, isLoading } = useServiceConnection<ChainState>('wallet');
  const { save, forget } = useServiceConnectionActions<{ url: string; kind: ChainKind }, SaveResult>('wallet');

  const [url, setUrl] = useState('');
  const [kind, setKind] = useState<ChainKind>('esplora');
  const [error, setError] = useState<string | null>(null);
  const [height, setHeight] = useState<number | null>(null);

  // Seeded from whatever is in use, so the form starts as the answer to "what is this pointed at" and the
  // owner edits it rather than retyping it. Keyed on both fields: a save or reset should re-seed.
  useEffect(() => {
    if (!data) return;
    setUrl(data.esploraUrl);
    setKind(data.kind);
  }, [data?.esploraUrl, data?.kind]);

  const trimmed = url.trim();
  const dirty = !!data && (trimmed !== data.esploraUrl || kind !== data.kind);
  const busy = save.isPending || forget.isPending;
  const meta = KINDS[kind];

  const submit = async () => {
    setError(null);
    setHeight(null);
    try {
      const result = await save.mutateAsync({ url: trimmed, kind });
      setHeight(result.probe?.blockHeight ?? null);
    } catch (err) {
      setError(serviceErrorMessage(err));
    }
  };

  const reset = async () => {
    setError(null);
    setHeight(null);
    try {
      await forget.mutateAsync();
    } catch (err) {
      setError(serviceErrorMessage(err));
    }
  };

  /** Switching protocol clears a URL that belongs to the other one — it would only fail the probe. */
  const pick = (next: ChainKind) => {
    if (next === kind) return;
    setKind(next);
    setError(null);
    setHeight(null);
    if (data && trimmed === data.esploraUrl && next !== data.kind) setUrl('');
  };

  return (
    <section className="max-w-2xl rounded-xl border border-border p-4">
      <h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">Chain source</h3>
      <p className="mb-3 text-xs text-muted-foreground">
        Where on-chain wallets read balances and history. It sees every address this wallet asks about, so your own node
        is a privacy upgrade — it never sees a key and cannot spend.
      </p>

      <div
        role="radiogroup"
        aria-label="Chain source protocol"
        className="mb-2 inline-flex rounded-lg border border-border p-0.5"
      >
        {(Object.keys(KINDS) as ChainKind[]).map((k) => (
          <button
            key={k}
            type="button"
            role="radio"
            aria-checked={kind === k}
            onClick={() => pick(k)}
            disabled={isLoading || busy}
            className={`rounded-md px-3 py-1 text-xs font-medium transition-colors disabled:opacity-50 ${
              kind === k ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:text-foreground'
            }`}
          >
            {KINDS[k].label}
          </button>
        ))}
      </div>

      <div className="flex flex-col gap-2 sm:flex-row">
        <Input
          value={url}
          onChange={(ev) => setUrl(ev.target.value)}
          onKeyDown={(ev) => {
            if (ev.key === 'Enter' && trimmed && dirty && !busy) void submit();
          }}
          placeholder={meta.placeholder}
          disabled={isLoading || busy}
          className="h-8 font-mono text-xs"
          aria-label={`${meta.label} API URL`}
          autoComplete="off"
          spellCheck={false}
        />
        <div className="flex shrink-0 gap-2">
          <Button size="sm" onClick={submit} disabled={!trimmed || !dirty || busy}>
            {save.isPending ? <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" /> : null}
            {save.isPending ? 'Connecting…' : 'Save & connect'}
          </Button>
          {data?.custom && (
            <Button size="sm" variant="outline" onClick={reset} disabled={busy} title="Back to the public default">
              <RotateCcw className="mr-1.5 h-3.5 w-3.5" />
              Reset
            </Button>
          )}
        </div>
      </div>

      {error && (
        <p className="mt-2 flex items-start gap-1.5 text-xs text-destructive">
          <TriangleAlert className="mt-0.5 h-3.5 w-3.5 shrink-0" />
          <span>{error}</span>
        </p>
      )}

      {height != null && !error && (
        <p className="mt-2 flex items-center gap-1.5 text-xs text-emerald-500">
          <CheckCircle2 className="h-3.5 w-3.5 shrink-0" />
          Connected — block {height.toLocaleString()}
        </p>
      )}

      <p className="mt-2 text-[11px] text-muted-foreground">
        {data?.custom
          ? `Using your ${KINDS[data.kind].label} endpoint.`
          : `Using the built-in default (${data?.defaultUrl ?? '—'}), which rate-limits and can see every address you look up.`}{' '}
        {meta.hint}
      </p>
    </section>
  );
};
