/**
 * x402 Hono middleware — reusable payment gate for rSpace modules.
 *
 * Supports two payment schemes:
 * - "exact": On-chain USDC via x402 facilitator (original)
 * - "crdt": Local cUSDC balance via CRDT token ledger (bridge)
 *
 * When X402_PAY_TO env is set, protects routes with x402 micro-transactions.
 * When not set, acts as a no-op passthrough.
 */

import type { Context, Next, MiddlewareHandler } from "hono";
import type { CrdtPaymentPayload } from "./crdt-scheme";

export interface CrdtPaymentConfig {
  tokenId: string;
  amount: number;
  payToDid: string;
  payToLabel: string;
  // Injected to avoid shared/ → server/ import
  getBalance: (doc: any, did: string) => number;
  getTokenDoc: (tokenId: string) => any;
  transferTokens: (
    tokenId: string,
    fromDid: string,
    fromLabel: string,
    toDid: string,
    toLabel: string,
    amount: number,
    memo: string,
    issuedBy: string,
  ) => boolean;
}

export interface X402Config {
  payTo: string;
  network: string;
  amount: string;
  facilitatorUrl: string;
  resource?: string;
  description?: string;
  /** Called after on-chain payment is verified (Direction 1: on-chain → CRDT mint) */
  onPaymentSettled?: (params: { paymentHeader: string; context: Context }) => Promise<void>;
  /** CRDT payment config (Direction 2: CRDT balance as payment) */
  crdtPayment?: CrdtPaymentConfig;
}

/**
 * Create x402 payment middleware for Hono routes.
 */
export function createX402Middleware(config: X402Config): MiddlewareHandler {
  // Lazy-load crdt-scheme to avoid circular imports
  let verifyCrdtPayment: typeof import("./crdt-scheme").verifyCrdtPayment | null = null;

  return async (c: Context, next: Next) => {
    const paymentHeader = c.req.header("X-PAYMENT");

    if (!paymentHeader) {
      // Build payment requirements array with available schemes
      const requirements: any[] = [
        {
          scheme: "exact",
          network: config.network,
          maxAmountRequired: config.amount,
          resource: config.resource || c.req.url,
          description: config.description || "Payment required",
          payTo: config.payTo,
          maxTimeoutSeconds: 300,
        },
      ];

      // Add CRDT option if configured
      if (config.crdtPayment) {
        requirements.push({
          scheme: "crdt",
          network: "rspace:local",
          tokenId: config.crdtPayment.tokenId,
          maxAmountRequired: String(config.crdtPayment.amount),
          resource: config.resource || c.req.url,
          description: config.description || "Payment required",
          payTo: config.crdtPayment.payToDid,
        });
      }

      return c.json(
        {
          error: "Payment Required",
          x402Version: 1,
          paymentRequirements: requirements,
        },
        402,
        { "X-PAYMENT-REQUIREMENTS": JSON.stringify(requirements) }
      );
    }

    // Decode payment header to detect scheme
    let decoded: any;
    try {
      decoded = JSON.parse(
        typeof atob === "function"
          ? atob(paymentHeader)
          : Buffer.from(paymentHeader, "base64").toString("utf-8")
      );
    } catch {
      // Not base64 JSON — treat as legacy exact scheme
      decoded = { scheme: "exact" };
    }

    // ── CRDT scheme ──
    if (decoded.scheme === "crdt" && config.crdtPayment) {
      try {
        // Lazy-load verifier
        if (!verifyCrdtPayment) {
          const mod = await import("./crdt-scheme");
          verifyCrdtPayment = mod.verifyCrdtPayment;
        }

        const payload = decoded as CrdtPaymentPayload;
        const result = await verifyCrdtPayment(
          payload,
          config.crdtPayment.amount,
          config.crdtPayment.tokenId,
        );

        if (!result.valid) {
          return c.json({ error: "CRDT payment invalid", details: result.error }, 402);
        }

        // Check balance
        const doc = config.crdtPayment.getTokenDoc(config.crdtPayment.tokenId);
        if (!doc) {
          return c.json({ error: "Token ledger not found" }, 500);
        }

        const balance = config.crdtPayment.getBalance(doc, result.fromDid!);
        if (balance < config.crdtPayment.amount) {
          return c.json({
            error: "Insufficient cUSDC balance",
            required: config.crdtPayment.amount,
            available: balance,
          }, 402);
        }

        // Execute transfer: payer → treasury
        const transferred = config.crdtPayment.transferTokens(
          config.crdtPayment.tokenId,
          result.fromDid!,
          result.fromLabel || result.fromDid!,
          config.crdtPayment.payToDid,
          config.crdtPayment.payToLabel,
          config.crdtPayment.amount,
          `x402 CRDT payment for ${config.resource || c.req.url}`,
          'x402-crdt',
        );

        if (!transferred) {
          return c.json({ error: "CRDT transfer failed" }, 500);
        }

        c.set("x402Payment", paymentHeader);
        c.set("x402Scheme", "crdt");
        await next();
        return;
      } catch (e) {
        console.error("[x402] CRDT verification error:", e);
        return c.json({ error: "CRDT payment verification failed" }, 500);
      }
    }

    // ── Exact (on-chain) scheme ──
    try {
      const verifyRes = await fetch(`${config.facilitatorUrl}/verify`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          payment: paymentHeader,
          requirements: {
            scheme: "exact",
            network: config.network,
            maxAmountRequired: config.amount,
            payTo: config.payTo,
          },
        }),
      });

      if (!verifyRes.ok) {
        const err = await verifyRes.text();
        return c.json({ error: "Payment verification failed", details: err }, 402);
      }

      const result = await verifyRes.json() as { valid?: boolean };
      if (!result.valid) {
        return c.json({ error: "Payment invalid or insufficient" }, 402);
      }

      // Payment valid — store tx info for downstream handlers
      c.set("x402Payment", paymentHeader);
      c.set("x402Scheme", "exact");

      // Direction 1: fire onPaymentSettled hook (mint cUSDC from on-chain)
      if (config.onPaymentSettled) {
        try {
          await config.onPaymentSettled({ paymentHeader, context: c });
        } catch (e) {
          console.warn("[x402] onPaymentSettled hook error (non-fatal):", e);
        }
      }

      await next();
    } catch (e) {
      console.error("[x402] Verification error:", e);
      return c.json({ error: "Payment verification service unavailable" }, 503);
    }
  };
}

/**
 * Initialize x402 from environment variables.
 * Returns middleware or null if disabled.
 */
export function setupX402FromEnv(overrides?: Partial<X402Config>): MiddlewareHandler | null {
  const payTo = process.env.X402_PAY_TO;
  if (!payTo) {
    console.log("[x402] Disabled — X402_PAY_TO not set");
    return null;
  }

  const config: X402Config = {
    payTo,
    network: process.env.X402_NETWORK || "eip155:84532",
    amount: process.env.X402_UPLOAD_PRICE || "0.01",
    facilitatorUrl: process.env.X402_FACILITATOR_URL || "https://www.x402.org/facilitator",
    ...overrides,
  };

  console.log(`[x402] Enabled — payTo=${payTo}, network=${config.network}, amount=${config.amount}`);
  return createX402Middleware(config);
}
