import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
import { AlertTriangle, Loader2, Bitcoin, Copy, Check } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { QRCodeCanvas } from '@/components/ui/qrcode';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { BitcoinAmountPicker } from '@/components/BitcoinAmountPicker';
import { getBitcoinFeeRate, getUniqueBitcoinFeeSpeeds } from '@/lib/bitcoinFeeSpeed';

import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useBitcoinSigner } from '@/hooks/useBitcoinSigner';
import { useOnchainZap, type OnchainFeeSpeed } from '@/hooks/useOnchainZap';
import { useToast } from '@/hooks/useToast';
import { useAppContext } from '@/hooks/useAppContext';
import { useNostrLogin } from '@nostrify/react/login';
import {
  nostrPubkeyToBitcoinAddress,
  fetchUTXOs,
  fetchBtcPrice,
  getFeeRates,
  estimateFee,
  isLargeAmount,
  satsToUSD,
  formatSats,
} from '@/lib/bitcoin';
import type { NostrEvent } from '@nostrify/nostrify';

const USD_PRESETS = [1, 5, 10, 25, 100];

const FEE_SPEED_LABELS: Record<OnchainFeeSpeed, string> = {
  fastest: '~10 min',
  halfHour: '~30 min',
  hour: '~1 hour',
  economy: '~1 day',
};

interface OnchainZapContentProps {
  target: NostrEvent;
  /** Called with the tx result when a zap successfully broadcasts. */
  onSuccess?: (result: { txid: string; amountSats: number }) => void;
  /** Called when the user dismisses without a send (e.g. "Done" in the
   * unsupported-signer QR fallback). */
  onClose?: () => void;
}

/**
 * Bitcoin zap flow. Publishes a BTC transaction paying the target author's
 * derived Taproot address, then publishes a kind 8333 event linking the tx
 * to the target event.
 *
 * UX mirrors the Lightning zap flow: one screen, one button, no review step.
 * Balance, fee breakdown, and confirmation are all hidden unless needed.
 */
export function OnchainZapContent({ target, onSuccess, onClose }: OnchainZapContentProps) {
  const { user } = useCurrentUser();
  const { capability } = useBitcoinSigner();
  const { logins } = useNostrLogin();
  const { config } = useAppContext();
  const { esploraApis } = config;
  const loginType = logins[0]?.type;

  const [usdAmount, setUsdAmount] = useState<number | string>(5);
  const [feeSpeed, setFeeSpeed] = useState<OnchainFeeSpeed>('halfHour');
  const [error, setError] = useState('');
  const [feePopoverOpen, setFeePopoverOpen] = useState(false);

  // Tracks whether the user has manually picked a fee speed. Once true, we
  // stop auto-adjusting the fee in response to amount changes.
  const feeSpeedUserChanged = useRef(false);

  const senderAddress = user ? nostrPubkeyToBitcoinAddress(user.pubkey) : '';
  const recipientAddress = useMemo(() => nostrPubkeyToBitcoinAddress(target.pubkey), [target.pubkey]);
  const truncatedRecipient = recipientAddress
    ? `${recipientAddress.slice(0, 10)}…${recipientAddress.slice(-8)}`
    : '';

  const { data: btcPrice } = useQuery({
    queryKey: ['btc-price', esploraApis],
    queryFn: ({ signal }) => fetchBtcPrice(esploraApis, signal),
    staleTime: 30_000,
  });

  const { data: utxos } = useQuery({
    queryKey: ['bitcoin-utxos', esploraApis, senderAddress],
    queryFn: ({ signal }) => fetchUTXOs(senderAddress, esploraApis, signal),
    enabled: !!senderAddress && capability !== 'unsupported',
    staleTime: 30_000,
  });

  const { data: feeRates } = useQuery({
    queryKey: ['bitcoin-fee-rates', esploraApis],
    queryFn: ({ signal }) => getFeeRates(esploraApis, signal),
    enabled: capability !== 'unsupported',
    staleTime: 30_000,
  });

  const totalBalance = useMemo(() => utxos?.reduce((s, u) => s + u.value, 0) ?? 0, [utxos]);

  const currentFeeRate = useMemo(() => {
    if (!feeRates) return 0;
    return getBitcoinFeeRate(feeRates, feeSpeed);
  }, [feeRates, feeSpeed]);

  // Convert the USD amount to sats
  const amountSats = useMemo(() => {
    if (!btcPrice) return 0;
    const usd = typeof usdAmount === 'string' ? parseFloat(usdAmount) : usdAmount;
    if (!Number.isFinite(usd) || usd <= 0) return 0;
    const btc = usd / btcPrice;
    return Math.round(btc * 100_000_000);
  }, [usdAmount, btcPrice]);

  const estimatedFeeSats = useMemo(() => {
    if (!utxos?.length || !currentFeeRate || !amountSats) return 0;
    const fee2 = estimateFee(utxos.length, 2, currentFeeRate);
    const change = totalBalance - amountSats - fee2;
    const numOutputs = change > 546 ? 2 : 1;
    return estimateFee(utxos.length, numOutputs, currentFeeRate);
  }, [utxos, currentFeeRate, amountSats, totalBalance]);

  const totalSats = amountSats + estimatedFeeSats;
  const insufficient = totalBalance > 0 && totalSats > totalBalance;
  const showBalance = insufficient || (amountSats > 0 && totalBalance === 0);

  // Auto-adjust fee speed when the amount changes, unless the user has
  // already picked a speed manually. Aim for a fee below 40% of the amount
  // by stepping down through the unique speed tiers. If every tier still
  // blows past 40% (tiny amount), fall back to the cheapest tier so we at
  // least minimize the hit.
  useEffect(() => {
    if (feeSpeedUserChanged.current) return;
    if (!utxos?.length || !feeRates || amountSats <= 0) return;

    const uniqueSpeeds = getUniqueBitcoinFeeSpeeds(feeRates);
    const threshold = amountSats * 0.4;

    let target: OnchainFeeSpeed = uniqueSpeeds[uniqueSpeeds.length - 1];
    for (const speed of uniqueSpeeds) {
      const rate = getBitcoinFeeRate(feeRates, speed);
      const fee2 = estimateFee(utxos.length, 2, rate);
      const change = totalBalance - amountSats - fee2;
      const outputs = change > 546 ? 2 : 1;
      const fee = estimateFee(utxos.length, outputs, rate);
      if (fee <= threshold) {
        target = speed;
        break;
      }
    }

    setFeeSpeed((prev) => (prev === target ? prev : target));
  }, [amountSats, feeRates, utxos, totalBalance]);

  const handleFeeSpeedChange = useCallback((speed: OnchainFeeSpeed) => {
    feeSpeedUserChanged.current = true;
    setFeeSpeed(speed);
    setFeePopoverOpen(false);
  }, []);

  // For large amounts, require a two-tap confirmation on the primary button.
  // This catches fat-finger sends without nagging on normal amounts.
  const isLarge = isLargeAmount(totalSats, btcPrice);
  const [confirmArmed, setConfirmArmed] = useState(false);

  // Re-arm (i.e. clear confirmation) whenever the amount, fee rate, or price
  // moves — so editing after arming forces another deliberate click.
  useEffect(() => {
    setConfirmArmed(false);
  }, [amountSats, currentFeeRate, btcPrice]);

  const { zapAsync, isZapping, progress } = useOnchainZap(target, (result) => {
    // Forward the txid + amount so the dialog can render its success screen.
    onSuccess?.({ txid: result.txid, amountSats: result.amountSats });
  });

  const handleZap = useCallback(async () => {
    setError('');
    if (!user) { setError('You must be logged in.'); return; }
    if (user.pubkey === target.pubkey) { setError("You can't zap yourself."); return; }
    // `capability === 'unsupported'` is already handled by the UI replacement
    // above; 'supported' and 'unknown' both proceed (the latter may fail at
    // sign time, which will then flip the UI to the unsupported state).
    if (!btcPrice) { setError('Waiting for BTC price…'); return; }
    if (amountSats <= 0) { setError('Enter an amount.'); return; }
    if (!utxos?.length) { setError("You don't have any Bitcoin yet. Receive some first."); return; }
    if (insufficient) { setError('Not enough Bitcoin for this amount + network fee.'); return; }

    // Two-tap safety for large amounts: first click arms, second click sends.
    if (isLarge && !confirmArmed) {
      setConfirmArmed(true);
      return;
    }

    try {
      await zapAsync({ amountSats, comment: '', feeSpeed });
      // onSuccess (passed to useOnchainZap) closes the dialog; toast is shown by the hook.
    } catch (err) {
      // Capability errors flip the UI via `reportSignerUnsupported` in the
      // hook's `onError`; no need to surface a form-level error for those.
      const msg = err instanceof Error ? err.message : 'Zap failed';
      const isCapability = /does not support|doesn't support|signpsbt|sign_psbt/i.test(msg);
      if (!isCapability) setError(msg);
    }
  }, [user, target.pubkey, btcPrice, amountSats, utxos, insufficient, zapAsync, feeSpeed, isLarge, confirmArmed]);

  // ── Signer not supported ──────────────────────────────────────
  // The user's signer can't sign PSBTs locally (extension without signPsbt,
  // or a bunker that rejected sign_psbt). Instead of a dead-end, show a QR
  // they can scan with any external Bitcoin wallet. We can't observe the
  // resulting txid, so we don't publish a kind 8333 — the user is warned
  // that the zap won't be attributed to them on Nostr.

  const currentUsd = typeof usdAmount === 'string' ? parseFloat(usdAmount) : usdAmount;
  const hasValidAmount = Number.isFinite(currentUsd) && currentUsd > 0;
  const totalUsdString = btcPrice ? satsToUSD(totalSats, btcPrice) : '';
  const uniqueFeeSpeeds = useMemo(() => getUniqueBitcoinFeeSpeeds(feeRates), [feeRates]);

  if (user && capability === 'unsupported') {
    return (
      <UnsupportedSignerQR
        recipientAddress={recipientAddress}
        truncatedRecipient={truncatedRecipient}
        amountSats={amountSats}
        btcPrice={btcPrice}
        usdAmount={usdAmount}
        setUsdAmount={setUsdAmount}
        loginType={loginType}
        onClose={onClose}
      />
    );
  }

  return (
    <div className="grid gap-4 px-4 py-4 w-full overflow-hidden">
      <BitcoinAmountPicker
        usdAmount={usdAmount}
        onUsdAmountChange={setUsdAmount}
        presets={USD_PRESETS}
        insufficient={insufficient}
        onAmountChangeStart={() => setError('')}
      />

      {/* Error */}
      {error && (
        <p className="text-xs text-destructive">{error}</p>
      )}

      <Button
        onClick={handleZap}
        disabled={!btcPrice || amountSats <= 0 || isZapping || insufficient}
        variant={(insufficient || isLarge) && !isZapping ? 'destructive' : 'default'}
        className="w-full"
      >
        {isZapping ? (
          <>
            <Loader2 className="size-4 mr-1.5 animate-spin" />
            {progressLabel(progress)}
          </>
        ) : insufficient ? (
          <>Not enough Bitcoin</>
        ) : isLarge && confirmArmed ? (
          <>Tap again to send {totalUsdString}</>
        ) : (
          <>Send {totalUsdString || (hasValidAmount ? `$${currentUsd}` : '')}</>
        )}
      </Button>

      {/* Fee line — click to open speed picker */}
      {amountSats > 0 && (
        <div className="flex items-center justify-center gap-3 -mt-1 text-xs">
          <Popover open={feePopoverOpen} onOpenChange={setFeePopoverOpen}>
            <PopoverTrigger asChild>
              <button
                type="button"
                className="inline-flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
              >
                <span>
                  Fee{' '}
                  {estimatedFeeSats > 0 && btcPrice
                    ? `≈ ${satsToUSD(estimatedFeeSats, btcPrice)}`
                    : currentFeeRate
                      ? `${currentFeeRate} sat/vB`
                      : 'loading'}
                  <span className="opacity-60"> · {FEE_SPEED_LABELS[feeSpeed]}</span>
                </span>
              </button>
            </PopoverTrigger>
            <PopoverContent align="center" sideOffset={6} className="w-56 p-1">
              <div className="flex flex-col">
                {uniqueFeeSpeeds.map((speed) => {
                  const rate = feeRates ? getBitcoinFeeRate(feeRates, speed) : 0;
                  const selected = speed === feeSpeed;
                  return (
                    <button
                      key={speed}
                      type="button"
                      onClick={() => handleFeeSpeedChange(speed)}
                      className={`flex items-center justify-between px-2 py-1.5 rounded-sm text-xs text-left hover:bg-muted transition-colors ${selected ? 'bg-muted font-medium' : ''}`}
                    >
                      <span>{FEE_SPEED_LABELS[speed]}</span>
                      <span className="text-muted-foreground">{rate} sat/vB</span>
                    </button>
                  );
                })}
              </div>
            </PopoverContent>
          </Popover>

          {showBalance && !insufficient && btcPrice && (
            <span className="text-muted-foreground">
              Balance: {satsToUSD(totalBalance, btcPrice)}
            </span>
          )}
        </div>
      )}
    </div>
  );
}

function progressLabel(progress: 'idle' | 'building' | 'signing' | 'broadcasting' | 'publishing'): string {
  switch (progress) {
    case 'building': return 'Building…';
    case 'signing': return 'Signing…';
    case 'broadcasting': return 'Broadcasting…';
    case 'publishing': return 'Publishing…';
    default: return 'Processing…';
  }
}

// ──────────────────────────────────────────────────────────────
// Unsupported-signer QR fallback
// ──────────────────────────────────────────────────────────────

interface UnsupportedSignerQRProps {
  recipientAddress: string;
  truncatedRecipient: string;
  amountSats: number;
  btcPrice: number | undefined;
  usdAmount: number | string;
  setUsdAmount: (v: number | string) => void;
  loginType: string | undefined;
  onClose?: () => void;
}

/**
 * Fallback shown when the user's signer can't sign PSBTs locally. Renders a
 * BIP-21 QR the user can scan with any external Bitcoin wallet. Because we
 * never see the resulting tx, we skip publishing the kind 8333 zap event and
 * explicitly warn the user about that.
 */
function UnsupportedSignerQR({
  recipientAddress,
  truncatedRecipient,
  amountSats,
  btcPrice,
  usdAmount,
  setUsdAmount,
  loginType,
  onClose,
}: UnsupportedSignerQRProps) {
  const { toast } = useToast();
  const [copied, setCopied] = useState<'address' | 'uri' | null>(null);

  // BIP-21 URI. Include `amount` (in BTC, 8 decimals) only when > 0 so an
  // empty-amount placeholder QR doesn't include `?amount=0`.
  const bip21 = useMemo(() => {
    if (!recipientAddress) return '';
    if (amountSats <= 0) return `bitcoin:${recipientAddress}`;
    const btc = (amountSats / 100_000_000).toFixed(8);
    return `bitcoin:${recipientAddress}?amount=${btc}`;
  }, [recipientAddress, amountSats]);

  const explanation =
    loginType === 'extension'
      ? "Your browser extension can't sign Bitcoin transactions."
      : loginType === 'bunker'
        ? "Your remote signer can't sign Bitcoin transactions."
        : "Your signer can't sign Bitcoin transactions.";

  const copy = useCallback(
    async (value: string, which: 'address' | 'uri', label: string) => {
      try {
        await navigator.clipboard.writeText(value);
        setCopied(which);
        toast({ title: 'Copied', description: `${label} copied to clipboard` });
        setTimeout(() => setCopied(null), 2000);
      } catch {
        toast({ title: 'Copy failed', description: 'Please copy manually.', variant: 'destructive' });
      }
    },
    [toast],
  );

  const currentUsd = typeof usdAmount === 'string' ? parseFloat(usdAmount) : usdAmount;
  const hasAmount = amountSats > 0;

  return (
    <div className="grid gap-3 px-4 py-4 w-full overflow-hidden">
      <p className="text-xs text-muted-foreground">
        {explanation} You can still zap by scanning this QR from any Bitcoin wallet.
      </p>

      {/* Amount presets (USD) */}
      <ToggleGroup
        type="single"
        value={USD_PRESETS.includes(Number(usdAmount)) ? String(usdAmount) : ''}
        onValueChange={(v) => { if (v) setUsdAmount(Number(v)); }}
        className="grid grid-cols-5 gap-1 w-full"
      >
        {USD_PRESETS.map((v) => (
          <ToggleGroupItem
            key={v}
            value={String(v)}
            className="h-8 min-w-0 text-xs font-semibold px-1"
          >
            ${v}
          </ToggleGroupItem>
        ))}
      </ToggleGroup>

      <div className="flex items-center gap-2">
        <div className="h-px flex-1 bg-muted" />
        <span className="text-xs text-muted-foreground">OR</span>
        <div className="h-px flex-1 bg-muted" />
      </div>

      <div className="relative">
        <span className="absolute left-3 top-1/2 -translate-y-1/2 text-sm text-muted-foreground">$</span>
        <Input
          type="number"
          inputMode="decimal"
          min={0}
          step="0.01"
          placeholder="Custom amount (USD)"
          value={usdAmount}
          onChange={(e) => setUsdAmount(e.target.value)}
          className="pl-6"
        />
      </div>

      {/* QR / placeholder */}
      <div className="flex justify-center">
        {hasAmount && bip21 ? (
          <div className="bg-white p-3 rounded-xl" aria-label="Bitcoin payment QR code">
            <QRCodeCanvas value={bip21} size={220} level="M" className="block" />
          </div>
        ) : (
          <div className="size-[220px] rounded-xl border border-dashed flex items-center justify-center text-xs text-muted-foreground text-center px-4">
            {btcPrice
              ? 'Choose an amount above to generate a payment QR.'
              : 'Loading BTC price…'}
          </div>
        )}
      </div>

      {/* Amount summary */}
      {hasAmount && btcPrice && (
        <div className="text-center text-sm">
          <span className="font-medium">
            {currentUsd > 0 ? `$${currentUsd}` : ''}
          </span>
          <span className="text-muted-foreground">
            {' · '}{formatSats(amountSats)} sats
          </span>
        </div>
      )}

      {/* Recipient */}
      {recipientAddress && (
        <div className="flex items-center justify-between gap-2 text-xs text-muted-foreground">
          <div className="flex items-center gap-1.5 min-w-0">
            <Bitcoin className="size-3.5 text-orange-500 shrink-0" />
            <span className="shrink-0">To:</span>
            <span className="font-mono truncate" title={recipientAddress}>{truncatedRecipient}</span>
          </div>
        </div>
      )}

      {/* Copy buttons */}
      <div className="grid grid-cols-2 gap-2">
        <Button
          type="button"
          variant="outline"
          size="sm"
          onClick={() => copy(recipientAddress, 'address', 'Address')}
          disabled={!recipientAddress}
          className="text-xs"
        >
          {copied === 'address' ? <Check className="size-3.5 mr-1.5" /> : <Copy className="size-3.5 mr-1.5" />}
          Copy address
        </Button>
        <Button
          type="button"
          variant="outline"
          size="sm"
          onClick={() => copy(bip21, 'uri', 'Payment link')}
          disabled={!hasAmount || !bip21}
          className="text-xs"
        >
          {copied === 'uri' ? <Check className="size-3.5 mr-1.5" /> : <Copy className="size-3.5 mr-1.5" />}
          Copy link
        </Button>
      </div>

      {/* Warning: no kind 8333 will be published */}
      <Alert>
        <AlertTriangle className="size-4" />
        <AlertDescription className="text-xs">
          Because we can't see your transaction, this zap won't show up as yours on Nostr. The recipient will still get the Bitcoin.
        </AlertDescription>
      </Alert>

      {onClose && (
        <Button type="button" variant="secondary" onClick={onClose} className="w-full">
          Done
        </Button>
      )}
    </div>
  );
}
