import React, { useState } from 'react';
import { useFetch } from '../../hooks.js';
import { fetchWalletsBalances, fetchPca, pcaAgentAccount, describePcaError } from '../../api.js';
import { formatEth, formatEthTooltip } from '../../lib/formatEth.js';
import { usePcaStore } from '../../stores/pca.js';
import { classifyCoverage, type PcaCoverageResult } from '../../pca/coverage.js';
import { PcaModalShell } from './PcaModalShell.js';
import {
  WalletRow,
  useCopy,
  nativeGasSymbol,
  isTestnetChain,
} from '../../components/Pca/index.js';

// Testnet faucets for the native gas token (testnet-only CTA — §6.2). Only
// chains we have a known faucet for render a button; others show the copy alone.
const FAUCETS: Record<string, string> = {
  '84532': 'https://www.alchemy.com/faucets/base-sepolia',
  '11155111': 'https://www.alchemy.com/faucets/ethereum-sepolia',
  '10200': 'https://faucet.chiadochain.net/',
};
function faucetUrl(chainId: string | null | undefined): string | undefined {
  if (chainId == null) return undefined;
  const k = String(chainId);
  const numeric = k.includes(':') ? k.split(':').pop()! : k;
  return FAUCETS[numeric];
}

interface ProbeRow {
  wallet: string;
  /**
   * The CANONICAL coverage result for this wallet on the probed account — carried as the
   * discriminated union (#1344), NOT flattened into parallel `registered`/`covers`/
   * `adapterUnsupported` flags that could express impossible states. Every derived signal
   * (approved count, N1 coverage, S6 "Ready", the #1356 capability-gap split) reads the
   * single `.outcome` discriminant. A transport failure maps to the `inconclusive`
   * variant with `adapterUnsupported:false` (couldn't read ≠ capability gap).
   */
  coverage: PcaCoverageResult;
}

/** The S6 approval-row status derived from the single coverage discriminant. */
function sponsorRowStatus(c: PcaCoverageResult): { text: string; tone: 'success' | 'warn' | 'danger' | 'neutral' } {
  switch (c.outcome) {
    case 'covers':
      return { text: 'approved', tone: 'success' };
    case 'uncovered':
      return { text: 'approved, but that PCA is expired, swept, or out of budget - publishes will not get the discount', tone: 'warn' };
    case 'unregistered':
      return { text: 'not approved - ask the PCA owner to approve it', tone: 'danger' };
    case 'inconclusive':
      return { text: 'unknown', tone: 'neutral' };
  }
}

/**
 * S6 — Edge "get added to a PCA" panel. Share operational wallets with a PCA
 * owner, fund their native gas (the PCA covers TRAC, never gas — faucet CTA
 * testnet-only), and track approval against the owner-provided PCA id. Ready =
 * ≥1 wallet approved AND gas-funded → publish.
 */
export function GetSponsoredPanel({ onClose }: { onClose: () => void }) {
  const { data: wb, loading: walletsLoading, error: walletsError, refresh: refreshWallets } = useFetch(fetchWalletsBalances, [], 0);
  const wallets = wb?.wallets ?? [];
  const balances = wb?.balances ?? [];
  const walletsBodyError = typeof wb?.error === 'string' && wb.error.length > 0 ? wb.error : null;
  const walletsReadError = walletsError || walletsBodyError;
  const chainId = wb?.chainId ?? null;
  const gasSymbol = nativeGasSymbol(chainId);
  const faucet = isTestnetChain(chainId) ? faucetUrl(chainId) : undefined;

  const trackAccount = usePcaStore((s) => s.trackAccount);
  const { copied, copy } = useCopy();

  // GAP-3 discovery (#1344) — surface which PCA(s) already sponsor this node's wallets
  // (chain truth, untracked by definition — that's the point of the edge panel). PURE
  // discovery: it never auto-tracks (guardrail b) and never asserts coverage — it just
  // pre-fills the manual "Check approval" so the edge doesn't need the core to hand over
  // the id. A wallet is approved on at most one account; distinct non-null ids are shown.
  const walletsKey = wallets.join(',');
  const { data: discovered } = useFetch(
    async () => {
      const ws = wb?.wallets ?? [];
      if (ws.length === 0) return [] as string[];
      const ids = await Promise.all(
        ws.map((w) => pcaAgentAccount(w).then((r) => r.accountId).catch(() => null)),
      );
      return Array.from(new Set(ids.filter((id): id is string => id != null)));
    },
    [walletsKey],
    0,
  );

  const [sponsorId, setSponsorId] = useState('');
  const [checking, setChecking] = useState(false);
  const [probeError, setProbeError] = useState<string | null>(null);
  const [probed, setProbed] = useState<{ accountId: string; rows: ProbeRow[] } | null>(null);

  const ethFor = (w: string) => balances.find((b) => b.address?.toLowerCase() === w.toLowerCase())?.eth;
  const isFunded = (w: string) => {
    const e = ethFor(w);
    return e != null && Number(e) > 0;
  };

  const check = async () => {
    const id = sponsorId.trim();
    if (!/^\d+$/.test(id)) { setProbeError('Enter the numeric PCA ID the owner gave you.'); return; }
    setChecking(true);
    setProbeError(null);
    try {
      const rows = await Promise.all(
        wallets.map((w) =>
          fetchPca(id, w, { extended: true })
            // #1344 — single canonical classifier (reads snap.probedKey); the row keeps the
            // whole discriminated result. S2 adapterSupported===false → inconclusive
            // (neutral "unknown" row, not a false not-approved; all-unsupported → wholesale
            // "not supported"); N1 coverage / S6 "Ready" == outcome 'covers'.
            .then((snap): ProbeRow => ({ wallet: w, coverage: classifyCoverage(snap) }))
            // A transport failure couldn't determine coverage → inconclusive, NOT a
            // capability gap (retry may resolve it).
            .catch((): ProbeRow => ({ wallet: w, coverage: { outcome: 'inconclusive', registered: null, adapterUnsupported: false } })),
        ),
      );
      // L4: the per-wallet .catch swallows failures, so every-row-null means the
      // lookup failed wholesale (vs genuine 0-approved, which is registered:false).
      // Surface that as an error — don't render a false "0 of N approved".
      if (rows.length > 0 && rows.every((r) => r.coverage.outcome === 'inconclusive')) {
        // #1356 — split the CAPABILITY GAP (the chain adapter can't answer PCA
        // queries here — retrying is futile) from a transient RPC failure (retry).
        // Only claim "not supported" when EVERY inconclusive probe is an adapter gap.
        const allAdapterGap = rows.every((r) => r.coverage.outcome === 'inconclusive' && r.coverage.adapterUnsupported);
        setProbeError(
          allAdapterGap
            ? 'PCA approval isn’t verifiable on this chain’s adapter — not supported here. You can still publish without a discount.'
            : `Couldn’t check approval on PCA #${id} right now — the chain lookup failed. Retry.`,
        );
        setProbed(null);
        return;
      }
      // N2: persist the sponsor id once ≥1 wallet is approved so the overview/chip
      // pick it up (NOT on the wholesale-failure path). Downstream re-probes the
      // chain independently, so nothing trusts this S6 result — safe vs #9.
      if (rows.some((r) => r.coverage.registered === true)) trackAccount(id);
      setProbed({ accountId: id, rows });
    } catch (err) {
      setProbeError(describePcaError(err, { accountId: id })?.message ?? 'Couldn’t check approval.');
    } finally {
      setChecking(false);
    }
  };

  const approvedCount = probed?.rows.filter((r) => r.coverage.registered === true).length ?? 0;
  // N1: Ready requires actual COVERAGE (spendable PCA), not bare registration.
  const ready = !!probed && probed.rows.some((r) => r.coverage.outcome === 'covers' && isFunded(r.wallet));

  return (
    <PcaModalShell
      onClose={onClose}
      testId="pca-get-sponsored"
      title="Get added to a PCA"
      subtitle="Share this node's operational publishing wallets with the PCA owner. They approve them; this node still pays its own gas."
    >
      <div className="v10-modal-body v10-pca-get-added-body">
        {/* Wallets to share */}
        <section className="v10-pca-detail-section">
          <h3 className="v10-pca-detail-section-title">Your operational wallets (signers / msg.sender)</h3>
          <div className="v10-pca-detail-agentlist">
            {/* Gate the empty state on load/error — a false "No operational
                wallets" while the fetch is in flight (or on a transient error)
                reads, on the edge's ONLY path to a discount, as "I can't get
                sponsored". Empty copy shows ONLY when loaded AND truly zero. */}
            {walletsLoading && !wb ? (
              <p className="v10-pca-handshake-empty" role="status">Loading your wallets…</p>
            ) : walletsReadError && wallets.length === 0 ? (
              <p className="v10-modal-error" role="alert">
                Couldn’t load your wallets.{' '}
                <button type="button" className="v10-pca-card-btn" onClick={() => refreshWallets()}>Retry</button>
              </p>
            ) : wallets.length === 0 ? (
              <p className="v10-pca-handshake-empty">No operational wallets detected on this node.</p>
            ) : (
              wallets.map((w) => {
                const eth = ethFor(w);
                const funded = isFunded(w);
                return (
                  <WalletRow
                    key={w}
                    address={w}
                    gas={
                      eth != null ? (
                        <span title={formatEthTooltip(eth)}>
                          gas {formatEth(eth)} {gasSymbol} {funded ? '✓' : '⚠ fund to publish'}
                        </span>
                      ) : undefined
                    }
                  />
                );
              })
            )}
          </div>
          {wallets.length > 0 && (
            <button
              type="button"
              className="v10-pca-handshake-copyall"
              onClick={() => copy(wallets.join('\n'))}
              aria-label={copied ? 'Copied all wallets' : 'Copy all wallet addresses'}
            >
              {copied ? 'Copied' : `Copy all ${wallets.length}`}
            </button>
          )}
          <p className="v10-pca-detail-hint">
            Share <strong>all</strong> wallets (the node rotates its signer per publish) or pin to one.
            Share the exact address - a typo can be approved by the PCA owner but never match your real signer.
          </p>
        </section>

        {/* Gas nudge */}
        <div className="v10-modal-warning">
          ⚠ Gas is always on you. The PCA covers the TRAC publishing fee — never your gas. Fund each
          wallet with native {gasSymbol}.
          {faucet ? (
            <>{' '}<a href={faucet} target="_blank" rel="noreferrer">Open faucet ↗</a> (testnet only)</>
          ) : (
            <>{' '}Fund with native {gasSymbol} from your wallet.</>
          )}
        </div>

        {/* Approval tracker */}
        <section className="v10-pca-detail-section">
          <h3 className="v10-pca-detail-section-title">Can&apos;t find it automatically?</h3>
          {/* GAP-3 discovery — surface any sponsoring PCA we found on-chain; pre-fill the
              check (the user confirms; nothing is auto-tracked). */}
          <p className="v10-pca-detail-hint">
            If the PCA owner gives you an account id, check it here to track whether this node&apos;s wallets are covered.
          </p>
          {discovered && discovered.length > 0 && (
            <div className="v10-pca-discovered-check" role="status" data-testid="pca-sponsor-discovered">
              ◉ We found your wallet(s) already approved on {discovered.map((id) => `PCA #${id}`).join(', ')}.{' '}
              <button type="button" className="v10-pca-card-btn compact" onClick={() => setSponsorId(discovered[0])}>
                Check PCA #{discovered[0]}
              </button>
            </div>
          )}
          <div className="v10-pca-detail-form">
            <input
              className="v10-form-input"
              type="text"
              value={sponsorId}
              onChange={(e) => setSponsorId(e.target.value)}
              placeholder="PCA ID from owner (e.g. 7)"
              aria-label="PCA ID from owner"
            />
            <button type="button" className="v10-pca-card-btn primary" onClick={check} disabled={checking}>
              {checking ? 'Checking…' : 'Check approval'}
            </button>
          </div>
          {probeError && <p className="v10-modal-error" role="alert">{probeError}</p>}
          {probed && (
            <div className="v10-pca-approve-results" role="status" aria-live="polite">
              <p className="v10-pca-approve-summary">
                Approved on PCA #{probed.accountId}: {approvedCount} of {probed.rows.length} wallets ✓
              </p>
              {probed.rows.map((r) => {
                const s = sponsorRowStatus(r.coverage);
                return <WalletRow key={r.wallet} address={r.wallet} status={s.text} statusTone={s.tone} />;
              })}
            </div>
          )}
        </section>

        {ready && (
          <div className="v10-pca-create-success" role="status">
            Ready: at least 1 wallet is approved and gas-funded - this node can publish under PCA #{probed!.accountId}.{' '}
            {/* L9: no global publish target — the discount applies at any per-CG
                publish. Guidance hint, not an inert "Go to publish →" button. */}
            Publish from any context graph — the discount applies automatically to your approved wallets.
          </div>
        )}
      </div>

      <div className="v10-modal-footer">
        <button type="button" className="v10-modal-btn" onClick={onClose}>Close</button>
      </div>
    </PcaModalShell>
  );
}
