import { useCallback, useEffect, useState } from 'react';
import { useAccount, useSendTransaction } from 'wagmi';
import {
  exchangeApi,
  omnlApiHeaders,
  DEFAULT_TOKENS,
  fetchExchangeTokens,
  parseAmountToWei,
  type DexToken,
} from '../../config/dex';

export type SwapPlan = {
  amountOut?: string;
  plannerResponse?: {
    routePlan?: { minAmountOut?: string; legs?: { provider?: string }[] };
  };
  execution?: { contractAddress: string; encodedCalldata: string };
  error?: string;
};

export function useDbisSwap() {
  const { address, isConnected } = useAccount();
  const { sendTransactionAsync } = useSendTransaction();
  const [tokens, setTokens] = useState<DexToken[]>(DEFAULT_TOKENS);
  const [tokenIn, setTokenIn] = useState<DexToken>(DEFAULT_TOKENS[4]);
  const [tokenOut, setTokenOut] = useState<DexToken>(DEFAULT_TOKENS[5]);
  const [amountIn, setAmountIn] = useState('');
  const [slippageBps, setSlippageBps] = useState(50);
  const [plan, setPlan] = useState<SwapPlan | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [txHash, setTxHash] = useState<string | undefined>();
  const [ledger, setLedger] = useState<Record<string, unknown> | null>(null);
  const [monitor, setMonitor] = useState<Record<string, unknown> | null>(null);
  const [transferTo, setTransferTo] = useState('');
  const [transferAmount, setTransferAmount] = useState('');
  const [externalIban, setExternalIban] = useState('');
  const [loadAmount, setLoadAmount] = useState('');
  const [lastSettlement, setLastSettlement] = useState<Record<string, unknown> | null>(null);

  useEffect(() => {
    fetchExchangeTokens().then((list) => {
      setTokens(list);
      setTokenIn(list.find((t) => t.symbol === 'cUSDT') ?? list[4] ?? list[0]);
      setTokenOut(list.find((t) => t.symbol === 'cUSDC') ?? list[5] ?? list[1]);
    });
  }, []);

  const loadLedgers = useCallback(async () => {
    if (!address) return;
    try {
      const q = new URLSearchParams({ wallet: address });
      const res = await fetch(`${exchangeApi('/ledgers')}?${q}`);
      if (res.ok) setLedger(await res.json());
    } catch {
      /* optional */
    }
  }, [address]);

  const fetchQuote = useCallback(async () => {
    if (!amountIn) return;
    setLoading(true);
    setError(null);
    try {
      const wei = parseAmountToWei(amountIn, tokenIn.decimals);
      const q = new URLSearchParams({
        tokenIn: tokenIn.address,
        tokenOut: tokenOut.address,
        amountIn: wei,
      });
      const res = await fetch(`${exchangeApi('/quote')}?${q}`);
      if (!res.ok) throw new Error(await res.text());
      const quote = await res.json();
      setPlan({ amountOut: quote.amountOut ?? quote.expectedOutput });
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Quote failed');
    } finally {
      setLoading(false);
    }
  }, [amountIn, tokenIn, tokenOut]);

  const fetchPlan = useCallback(async () => {
    if (!amountIn || !address) return;
    setLoading(true);
    setError(null);
    try {
      const wei = parseAmountToWei(amountIn, tokenIn.decimals);
      const res = await fetch(exchangeApi('/execute-plan'), {
        method: 'POST',
        headers: omnlApiHeaders(),
        body: JSON.stringify({
          tokenIn: tokenIn.address,
          tokenOut: tokenOut.address,
          amountIn: wei,
          recipient: address,
          maxSlippageBps: slippageBps,
        }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || 'Plan failed');
      setPlan(data);
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Plan failed');
    } finally {
      setLoading(false);
    }
  }, [amountIn, tokenIn, tokenOut, address, slippageBps]);

  const settleSwap = useCallback(
    async (hash: string) => {
      try {
        const res = await fetch(exchangeApi('/swap/settle'), {
          method: 'POST',
          headers: omnlApiHeaders(),
          body: JSON.stringify({
            amount: amountIn,
            recipientAddress: address,
            currency: tokenIn.currencyCode ?? 'USD',
            tokenLineId: tokenOut.omnlLine ?? `${tokenOut.symbol}-M2`,
            tokenSymbol: tokenOut.symbol,
            remittanceInfo: `Swap ${tokenIn.symbol}→${tokenOut.symbol} tx ${hash}`,
          }),
        });
        if (res.ok) setLastSettlement(await res.json());
      } catch {
        /* optional M2 audit */
      }
    },
    [amountIn, address, tokenIn, tokenOut],
  );

  const executeSwap = useCallback(async () => {
    if (!plan?.execution || !address) {
      await fetchPlan();
      return;
    }
    setLoading(true);
    setError(null);
    try {
      const hash = await sendTransactionAsync({
        to: plan.execution.contractAddress as `0x${string}`,
        data: plan.execution.encodedCalldata as `0x${string}`,
      });
      setTxHash(hash);
      await fetch(exchangeApi('/swap/record'), {
        method: 'POST',
        headers: omnlApiHeaders(),
        body: JSON.stringify({
          tokenIn: tokenIn.address,
          tokenOut: tokenOut.address,
          amountIn: parseAmountToWei(amountIn, tokenIn.decimals),
          wallet: address,
          txHash: hash,
          status: 'SUBMITTED',
          routeProviders: plan.plannerResponse?.routePlan?.legs?.map((l) => l.provider).filter(Boolean),
        }),
      });
      await settleSwap(hash);
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Swap failed');
    } finally {
      setLoading(false);
    }
  }, [plan, address, sendTransactionAsync, fetchPlan, tokenIn, tokenOut, amountIn, settleSwap]);

  const loadM2Token = useCallback(async () => {
    if (!address || !loadAmount) return;
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(exchangeApi('/token-load'), {
        method: 'POST',
        headers: omnlApiHeaders(),
        body: JSON.stringify({
          idempotencyKey: `LOAD-${Date.now()}`,
          tokenAddress: tokenOut.address,
          symbol: tokenOut.symbol,
          lineId: tokenOut.omnlLine ?? 'USD-M2',
          amount: loadAmount,
          recipientAddress: address,
          currency: tokenOut.currencyCode ?? 'USD',
        }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || data.errors?.join(', ') || 'Token load failed');
      setLastSettlement(data);
      await loadLedgers();
    } catch (e) {
      setError(e instanceof Error ? e.message : 'M2 token load failed');
    } finally {
      setLoading(false);
    }
  }, [address, loadAmount, tokenOut, loadLedgers]);

  const internalTransfer = useCallback(async () => {
    if (!address || !transferTo || !transferAmount) return;
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(exchangeApi('/transfer/internal'), {
        method: 'POST',
        headers: omnlApiHeaders(),
        body: JSON.stringify({
          idempotencyKey: `XFER-INT-${Date.now()}`,
          tokenAddress: tokenIn.address,
          tokenSymbol: tokenIn.symbol,
          amount: transferAmount,
          recipientAddress: transferTo,
          senderAddress: address,
        }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || data.errors?.join(', ') || 'Transfer failed');
      setLastSettlement(data);
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Internal transfer failed');
    } finally {
      setLoading(false);
    }
  }, [address, transferTo, transferAmount, tokenIn]);

  const externalTransfer = useCallback(async () => {
    if (!address || !transferTo || !transferAmount || !externalIban) return;
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(exchangeApi('/transfer/external'), {
        method: 'POST',
        headers: omnlApiHeaders(),
        body: JSON.stringify({
          idempotencyKey: `XFER-EXT-${Date.now()}`,
          tokenAddress: tokenIn.address,
          tokenSymbol: tokenIn.symbol,
          amount: transferAmount,
          recipientAddress: transferTo,
          creditorIban: externalIban,
          remittanceInfo: `OMNL M2 external ${tokenIn.symbol}`,
        }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || data.errors?.join(', ') || 'External transfer failed');
      setLastSettlement(data);
    } catch (e) {
      setError(e instanceof Error ? e.message : 'External transfer failed');
    } finally {
      setLoading(false);
    }
  }, [address, transferTo, transferAmount, externalIban, tokenIn]);

  const loadMonitor = useCallback(async () => {
    try {
      const res = await fetch(`${exchangeApi('/swap/monitor')}?limit=20`);
      if (res.ok) setMonitor(await res.json());
    } catch {
      /* optional */
    }
  }, []);

  return {
    address,
    isConnected,
    tokens,
    tokenIn,
    setTokenIn,
    tokenOut,
    setTokenOut,
    amountIn,
    setAmountIn,
    slippageBps,
    setSlippageBps,
    plan,
    loading,
    error,
    txHash,
    ledger,
    monitor,
    transferTo,
    setTransferTo,
    transferAmount,
    setTransferAmount,
    externalIban,
    setExternalIban,
    loadAmount,
    setLoadAmount,
    lastSettlement,
    fetchQuote,
    fetchPlan,
    executeSwap,
    loadM2Token,
    internalTransfer,
    externalTransfer,
    loadLedgers,
    loadMonitor,
    parseAmountToWei,
  };
}
