import { ethers } from 'ethers';
import {
  type AgentCard,
  type EnrichedAgentCard,
  type EnrichOptions,
  type OnChainAgentData,
  type VerificationResult,
  type VaultfireExtensionData,
  type SupportedChain,
  CHAIN_CONFIGS,
  IDENTITY_ABI,
  PARTNERSHIP_ABI,
  REPUTATION_ABI,
  BRIDGE_ABI,
  getStreetCredTier,
  VaultfireExtensionDataSchema,
} from './types';

// ─── Provider Cache ────────────────────────────────────────────────────────────

const providerCache = new Map<SupportedChain, ethers.JsonRpcProvider>();

function getProvider(chain: SupportedChain): ethers.JsonRpcProvider {
  if (!providerCache.has(chain)) {
    const config = CHAIN_CONFIGS[chain];
    providerCache.set(chain, new ethers.JsonRpcProvider(config.rpcUrl));
  }
  return providerCache.get(chain)!;
}

// ─── Street Cred Calculation ──────────────────────────────────────────────────

/**
 * Calculate Street Cred score (0-95) from on-chain data.
 *
 * Scoring breakdown:
 *  - Identity registration + active status: up to 25 points
 *  - Partnership bonds (active): up to 30 points
 *  - Reputation (rating + feedbacks): up to 30 points
 *  - Cross-chain recognition (bridge): up to 10 points
 */
export function calculateStreetCred(data: OnChainAgentData): number {
  let score = 0;

  // Identity: 25 pts max
  if (data.identity.active) {
    score += 20;
    // Bonus for being long-standing (>30 days)
    const ageSecs = BigInt(Math.floor(Date.now() / 1000)) - data.identity.registeredAt;
    if (ageSecs > BigInt(30 * 24 * 3600)) {
      score += 5;
    }
  } else if (data.identity.registeredAt > 0n) {
    score += 5; // registered but inactive
  }

  // Bonds: 30 pts max
  const activeBonds = Math.min(data.bonds.active, 6);
  score += activeBonds * 5; // 5 pts per active bond, max 30

  // Reputation: 30 pts max
  if (data.reputation.totalFeedbacks > 0n) {
    const avgRating = Number(data.reputation.averageRating) / 100; // stored as x100
    const ratingScore = Math.min((avgRating / 5) * 15, 15); // up to 15 for rating
    const feedbackScore = Math.min(Number(data.reputation.totalFeedbacks) * 3, 15); // 3 pts per feedback, max 15
    score += Math.round(ratingScore + feedbackScore);
  }

  // Cross-chain recognition: 10 pts
  if (data.isRecognized) {
    score += 10;
  }

  return Math.min(score, 95);
}

// ─── On-chain Data Fetching ───────────────────────────────────────────────────

export async function fetchOnChainData(
  chain: SupportedChain,
  agentAddress: string
): Promise<OnChainAgentData> {
  const provider = getProvider(chain);
  const config = CHAIN_CONFIGS[chain];

  const identityContract = new ethers.Contract(config.contracts.identity, IDENTITY_ABI, provider);
  const partnershipContract = new ethers.Contract(
    config.contracts.partnership,
    PARTNERSHIP_ABI,
    provider
  );
  const reputationContract = new ethers.Contract(
    config.contracts.reputation,
    REPUTATION_ABI,
    provider
  );
  const bridgeContract = new ethers.Contract(config.contracts.bridge, BRIDGE_ABI, provider);

  // Fetch identity data
  let identityData = {
    agentURI: '',
    active: false,
    agentType: 'unknown',
    registeredAt: 0n,
  };

  try {
    const result = await identityContract.getAgent(agentAddress);
    identityData = {
      agentURI: result[0] ?? result.agentURI ?? '',
      active: result[1] ?? result.active ?? false,
      agentType: result[2] ?? result.agentType ?? 'unknown',
      registeredAt: result[3] ?? result.registeredAt ?? 0n,
    };
  } catch {
    // Agent not registered — leave defaults
  }

  // Fetch bonds
  let bondsData = { total: 0, active: 0 };
  try {
    const bondIds: bigint[] = await partnershipContract.getBondsByParticipant(agentAddress);
    let activeBonds = 0;
    // Check each bond (limit to 20 for performance)
    const toCheck = bondIds.slice(0, 20);
    const bondChecks = await Promise.allSettled(
      toCheck.map((id) => partnershipContract.getBond(id))
    );
    for (const result of bondChecks) {
      if (result.status === 'fulfilled') {
        const bond = result.value;
        const isActive = bond[8] ?? bond.active ?? false;
        if (isActive) activeBonds++;
      }
    }
    bondsData = { total: bondIds.length, active: activeBonds };
  } catch {
    // No bonds
  }

  // Fetch reputation
  let reputationData = {
    averageRating: 0n,
    totalFeedbacks: 0n,
    verifiedFeedbacks: 0n,
    lastUpdated: 0n,
  };
  try {
    const rep = await reputationContract.getReputation(agentAddress);
    reputationData = {
      averageRating: rep[0] ?? rep.averageRating ?? 0n,
      totalFeedbacks: rep[1] ?? rep.totalFeedbacks ?? 0n,
      verifiedFeedbacks: rep[2] ?? rep.verifiedFeedbacks ?? 0n,
      lastUpdated: rep[3] ?? rep.lastUpdated ?? 0n,
    };
  } catch {
    // No reputation data
  }

  // Check bridge recognition
  let isRecognized = false;
  try {
    isRecognized = await bridgeContract.isAgentRecognized(agentAddress);
  } catch {
    // Not recognized
  }

  return {
    identity: identityData,
    bonds: bondsData,
    reputation: reputationData,
    isRecognized,
  };
}

// ─── Build Extension Data ─────────────────────────────────────────────────────

export function buildExtensionData(
  chain: SupportedChain,
  address: string,
  onChain: OnChainAgentData
): VaultfireExtensionData {
  const score = calculateStreetCred(onChain);
  const tier = getStreetCredTier(score);

  const avgRating =
    onChain.reputation.totalFeedbacks > 0n
      ? Number(onChain.reputation.averageRating) / 100
      : 0;

  return {
    chain,
    address,
    streetCred: {
      score,
      tier,
      maxPossible: 95,
    },
    bonds: {
      total: onChain.bonds.total,
      active: onChain.bonds.active,
    },
    reputation: {
      averageRating: Math.round(avgRating * 100) / 100,
      totalFeedbacks: Number(onChain.reputation.totalFeedbacks),
    },
    identity: {
      registered: onChain.identity.registeredAt > 0n || onChain.identity.active,
      active: onChain.identity.active,
      agentType: onChain.identity.agentType || 'unknown',
    },
    verifiedAt: new Date().toISOString(),
  };
}

// ─── Main Exported Functions ──────────────────────────────────────────────────

/**
 * Enrich an A2A Agent Card with Vaultfire on-chain trust data.
 * Adds an `x-vaultfire` field to the card containing Street Cred score,
 * bond status, reputation, and identity information.
 */
export async function enrichAgentCard(options: EnrichOptions): Promise<EnrichedAgentCard> {
  const { agentCard, chain, agentAddress } = options;

  if (!agentAddress || !/^0x[0-9a-fA-F]{40}$/.test(agentAddress)) {
    throw new Error(`Invalid agent address: ${agentAddress}`);
  }

  if (!CHAIN_CONFIGS[chain]) {
    throw new Error(`Unsupported chain: ${chain}`);
  }

  const onChainData = await fetchOnChainData(chain, agentAddress);
  const extensionData = buildExtensionData(chain, agentAddress, onChainData);

  return {
    ...agentCard,
    'x-vaultfire': extensionData,
  };
}

/**
 * Verify an enriched Agent Card's Vaultfire trust claims against on-chain data.
 * Re-fetches live data and compares against the card's claimed values.
 */
export async function verifyAgentCard(card: EnrichedAgentCard): Promise<VerificationResult> {
  const extension = card['x-vaultfire'];

  if (!extension) {
    return {
      trusted: false,
      streetCred: { score: 0, tier: 'Newcomer', maxPossible: 95 },
      bonds: { total: 0, active: 0 },
      reputation: { averageRating: 0, totalFeedbacks: 0 },
      identity: { registered: false, active: false, agentType: 'unknown' },
      reason: 'No x-vaultfire extension found in Agent Card',
      chain: 'base',
      address: '',
      verifiedAt: new Date().toISOString(),
    };
  }

  // Validate the extension schema
  const parseResult = VaultfireExtensionDataSchema.safeParse(extension);
  if (!parseResult.success) {
    return {
      trusted: false,
      streetCred: extension.streetCred ?? { score: 0, tier: 'Newcomer', maxPossible: 95 },
      bonds: extension.bonds ?? { total: 0, active: 0 },
      reputation: extension.reputation ?? { averageRating: 0, totalFeedbacks: 0 },
      identity: extension.identity ?? { registered: false, active: false, agentType: 'unknown' },
      reason: `Invalid x-vaultfire extension schema: ${parseResult.error.message}`,
      chain: extension.chain,
      address: extension.address,
      verifiedAt: new Date().toISOString(),
    };
  }

  // Re-fetch live data to verify claims
  let liveData: OnChainAgentData;
  try {
    liveData = await fetchOnChainData(extension.chain, extension.address);
  } catch (err) {
    return {
      trusted: false,
      streetCred: extension.streetCred,
      bonds: extension.bonds,
      reputation: extension.reputation,
      identity: extension.identity,
      reason: `Failed to fetch on-chain data: ${err instanceof Error ? err.message : String(err)}`,
      chain: extension.chain,
      address: extension.address,
      verifiedAt: new Date().toISOString(),
    };
  }

  const liveExtension = buildExtensionData(extension.chain, extension.address, liveData);
  const scoreDrift = Math.abs(liveExtension.streetCred.score - extension.streetCred.score);

  // Trust thresholds: agent must be active and score drift < 15 points
  const trusted = liveExtension.identity.active && scoreDrift < 15;

  let reason: string;
  if (!liveExtension.identity.active) {
    reason = 'Agent is not currently active on-chain';
  } else if (scoreDrift >= 15) {
    reason = `Street Cred score drifted ${scoreDrift} points from claimed value`;
  } else {
    reason = `Verified: Street Cred ${liveExtension.streetCred.score} (${liveExtension.streetCred.tier}), ${liveExtension.bonds.active} active bonds`;
  }

  return {
    trusted,
    streetCred: liveExtension.streetCred,
    bonds: liveExtension.bonds,
    reputation: liveExtension.reputation,
    identity: liveExtension.identity,
    reason,
    chain: extension.chain,
    address: extension.address,
    verifiedAt: new Date().toISOString(),
  };
}
