/**
 * ERC-8128 verification middleware for theloopbreaker.com.
 *
 * Verifies inbound HTTP signatures (RFC 9421) against an Ethereum address,
 * then resolves the canonical Vaultfire Street Cred score by calling the
 * VaultfireStreetCred contract's computeScore() directly. This means the
 * on-chain contract is the single source of truth — no formula drift
 * between Solidity and TypeScript.
 *
 * Spec  : https://ethereum-magicians.org/t/erc-8128-signed-http-requests-with-ethereum/
 * RFC   : https://datatracker.ietf.org/doc/rfc9421/
 * Sprint: 4 (April 2026)
 */

import { verifyRequest, type NonceStore, type VerifyPolicy } from '@slicekit/erc8128';
import { ethers } from 'ethers';
import { withRetry } from './rpc-retry';

/* ────────────────────────── Constants ────────────────────────── */

export const VAULTFIRE_CHAIN_ID = 8453; // Base mainnet
export const STREET_CRED_ADDRESS = '0x5CfAB4B12718549e156D3642f9C63476aC4c2f99';

const RPC_URL = process.env.BASE_RPC_URL ?? 'https://base-rpc.publicnode.com';

/* ────────────────────────── Tier types ────────────────────────── */

export const TIER = {
  NONE: 0,
  BRONZE: 1,
  SILVER: 2,
  GOLD: 3,
  PLATINUM: 4,
} as const;
export type Tier = (typeof TIER)[keyof typeof TIER];

export const TIER_NAMES: Record<Tier, string> = {
  0: 'None',
  1: 'Bronze',
  2: 'Silver',
  3: 'Gold',
  4: 'Platinum',
};

/**
 * Map a Street Cred score (0–95) to its tier.
 * Mirrors VaultfireStreetCred.sol _scoreToTier() exactly:
 *   None=0, Bronze 1..30, Silver 31..55, Gold 56..75, Platinum 76..95.
 */
export function scoreToTier(score: number): Tier {
  if (score === 0) return TIER.NONE;
  if (score <= 30) return TIER.BRONZE;
  if (score <= 55) return TIER.SILVER;
  if (score <= 75) return TIER.GOLD;
  return TIER.PLATINUM;
}

/* ──────────────────────── In-memory nonce store ──────────────────────── */

/**
 * Process-local nonce store. Adequate for theloopbreaker.com's current
 * single-region serverless deployment given our 60s default TTL — replay
 * windows are short enough that the only realistic vector is replay
 * within the same Vercel function instance, which this catches.
 *
 * Auto-prunes expired entries each call.
 */
function createInMemoryNonceStore(): NonceStore {
  const store = new Map<string, number>();
  return {
    async consume(key: string, ttlSeconds: number): Promise<boolean> {
      const now = Date.now();
      for (const [k, exp] of store) if (exp <= now) store.delete(k);
      if (store.has(key)) return false;
      store.set(key, now + ttlSeconds * 1000);
      return true;
    },
  };
}

let _nonceStore: NonceStore | null = null;
function nonceStore(): NonceStore {
  if (!_nonceStore) _nonceStore = createInMemoryNonceStore();
  return _nonceStore;
}

/* ────────────────────── Street Cred score resolver ────────────────────── */

/**
 * Canonical ABI fragment for VaultfireStreetCred.computeScore().
 * This is the SINGLE call we make to resolve trust — the contract itself
 * applies the formula, reads identity registry, enumerates bonds, and
 * returns the same score that the on-chain badge tokenURI displays.
 */
const STREET_CRED_ABI = [
  'function computeScore(address agent) view returns (uint16 score, uint8 tier, bool registered, bool hasBond, bool bondActive, uint256 bondCount, uint256 stakeWei)',
] as const;

export type AgentTrust = {
  address: string;
  registered: boolean;
  hasBond: boolean;
  bondActive: boolean;
  bondCount: number;
  stakeWei: bigint;
  stakeEth: string;          // human-readable, e.g. "0.05"
  score: number;             // 0–95
  tier: Tier;                // 0–4 Street Cred tier
  tierName: string;
};

/**
 * Resolve canonical Street Cred trust for an agent by calling
 * VaultfireStreetCred.computeScore() on-chain. Single source of truth.
 */
export async function resolveAgentTrust(
  agent: string,
  provider?: ethers.JsonRpcProvider
): Promise<AgentTrust> {
  const p = provider ?? new ethers.JsonRpcProvider(RPC_URL, VAULTFIRE_CHAIN_ID);
  const streetCred = new ethers.Contract(STREET_CRED_ADDRESS, STREET_CRED_ABI, p);

  const result = await withRetry(() => streetCred.computeScore(agent));
  // ethers v6 returns a Result-like array with named accessors
  const score = Number(result[0]);
  const tier = Number(result[1]) as Tier;
  const registered = Boolean(result[2]);
  const hasBond = Boolean(result[3]);
  const bondActive = Boolean(result[4]);
  const bondCount = Number(result[5]);
  const stakeWei = BigInt(result[6]);

  return {
    address: agent.toLowerCase(),
    registered,
    hasBond,
    bondActive,
    bondCount,
    stakeWei,
    stakeEth: ethers.formatEther(stakeWei),
    score,
    tier,
    tierName: TIER_NAMES[tier] ?? 'None',
  };
}

/* ──────────────────────── Verification entrypoint ──────────────────────── */

export type VerifiedAgent = {
  address: string;
  chainId: number;
  keyid: string;
  trust: AgentTrust;
};

export type VerifyError =
  | { code: 'unsigned'; status: 401; reason: string }
  | { code: 'invalid_signature'; status: 401; reason: string; detail?: string }
  | { code: 'insufficient_tier'; status: 403; reason: string; required: Tier; actual: Tier; agent: AgentTrust };

/**
 * Verify an inbound Request's ERC-8128 signature, resolve the agent's
 * Street Cred score on-chain, and (optionally) gate by minimum tier.
 *
 * Returns either {ok: true, agent} or {ok: false, error}.
 *
 * @param request The Next.js Request object
 * @param opts.minTier Minimum required tier (default: TIER.NONE — verify only, no tier gate)
 * @param opts.policy ERC-8128 verify policy overrides
 */
export async function verifyAndResolve(
  request: Request,
  opts: { minTier?: Tier; policy?: VerifyPolicy } = {}
): Promise<{ ok: true; agent: VerifiedAgent } | { ok: false; error: VerifyError }> {
  const minTier = opts.minTier ?? TIER.NONE;

  // Signature presence check first — clearer error than slice-so's generic missing_headers
  if (!request.headers.get('signature') || !request.headers.get('signature-input')) {
    return {
      ok: false,
      error: {
        code: 'unsigned',
        status: 401,
        reason: 'Request is missing ERC-8128 Signature and/or Signature-Input headers',
      },
    };
  }

  // Verify signature (RFC 9421 + EIP-191 ecrecover)
  const verifyMessage = async (args: { address: `0x${string}`; message: { raw: `0x${string}` }; signature: `0x${string}` }) => {
    try {
      const messageBytes = ethers.getBytes(args.message.raw);
      const recovered = ethers.verifyMessage(messageBytes, args.signature);
      return recovered.toLowerCase() === args.address.toLowerCase();
    } catch {
      return false;
    }
  };

  const sigResult = await verifyRequest({
    request,
    nonceStore: nonceStore(),
    verifyMessage,
    policy: { maxValiditySec: 300, clockSkewSec: 5, ...opts.policy },
  });

  if (!sigResult.ok) {
    return {
      ok: false,
      error: {
        code: 'invalid_signature',
        status: 401,
        reason: sigResult.reason,
        detail: sigResult.detail,
      },
    };
  }

  // Resolve on-chain trust via canonical contract
  const trust = await resolveAgentTrust(sigResult.address);

  // Tier gate
  if (trust.tier < minTier) {
    return {
      ok: false,
      error: {
        code: 'insufficient_tier',
        status: 403,
        reason: `Agent tier ${trust.tierName} (${trust.tier}) is below required ${TIER_NAMES[minTier]} (${minTier})`,
        required: minTier,
        actual: trust.tier,
        agent: trust,
      },
    };
  }

  return {
    ok: true,
    agent: {
      address: sigResult.address,
      chainId: sigResult.chainId,
      keyid: sigResult.params.keyid,
      trust,
    },
  };
}
