/**
 * Bonds — Vaultfire's accountability layer.
 *
 * All ABIs in this file match the source contracts published at
 * gitlawb.com/.../vaultfire (canonical truth) and the verified deployments on
 * Base mainnet (chainId 8453). No invented method names.
 *
 * Bonds are commitment stake. They feed the Flourishing Metric oracle and are
 * arbitrated through VaultfireDisputeResolution. They are NOT punitively
 * slashed — they decay against the global flourishing score.
 *
 * Source: AIAccountabilityBondsV2.sol, AIPartnershipBondsV2.sol,
 *         VaultfireBondInsurancePool.sol
 */

import { ethers } from 'ethers';

// ─── Live Base mainnet contract addresses (verified on-chain) ─────────────
export const BOND_CONTRACTS = {
  base: {
    chainId: 8453,
    accountabilityBondsV2: '0x6750D28865434344e04e1D0a6044394b726C3dfE',
    partnershipBondsV2:    '0x01C479F0c039fEC40c0Cf1c5C921bab457d57441',
    bondInsurancePool:     '0xa074e372d5221b605E9762e41B86EbdBEBF1a978',
    disputeResolution:     '0x3a681b6eF98c6d35643Ca631eE50EF35edd6BcBD',
    flourishingOracle:     '0xf7B2dC73Ec69528F171F4a0ded62da557866BaA5',
    helixaV2:              '0x2e3B541C59D38b84E3Bc54e977200230A204Fe60',
  },
} as const;

// ─── Real ABIs — copied from canonical Solidity sources ───────────────────
const PARTNERSHIP_ABI = [
  // Writes
  'function createBond(address aiAgent, string partnershipType) payable returns (uint256)',
  'function requestDistribution(uint256 bondId)',
  'function distributeBond(uint256 bondId)',
  // Reads
  'function getBondsByParticipant(address) view returns (uint256[])',
  'function getBond(uint256) view returns (tuple(uint256 bondId, address human, address aiAgent, string partnershipType, uint256 stakeAmount, uint256 createdAt, uint256 distributionRequestedAt, bool distributionPending, bool active))',
  'function calculateBondValue(uint256 bondId) view returns (uint256)',
  'function calculateAppreciation(uint256 bondId) view returns (int256)',
  'function partnershipQualityScore(uint256 bondId) view returns (uint256)',
  'function loyaltyMultiplier(uint256 bondId) view returns (uint256)',
];

const ACCOUNTABILITY_ABI = [
  // Writes
  'function createBond(string companyName, uint256 quarterlyRevenue) payable returns (uint256)',
  'function requestDistribution(uint256 bondId)',
  'function distributeBond(uint256 bondId)',
  // Reads
  'function getBond(uint256) view returns (tuple(uint256 bondId, address aiCompany, string companyName, uint256 quarterlyRevenue, uint256 stakeAmount, uint256 createdAt, uint256 distributionRequestedAt, bool distributionPending, bool active))',
  'function globalFlourishingScore(uint256 bondId) view returns (uint256)',
  'function calculateBondValue(uint256 bondId) view returns (uint256)',
  'function calculateAppreciation(uint256 bondId) view returns (int256)',
  'function timeMultiplier(uint256 bondId) view returns (uint256)',
];

const INSURANCE_ABI = [
  // Writes
  'function contribute() payable',
  'function submitClaim(uint256 amount, string reason, bytes32 evidenceHash)',
  // Reads
  'function isInsured(address agent) view returns (bool)',
  'function getContributorProfile(address) view returns (uint256 totalContributed, uint256 contributionCount, bool insured, uint256 firstContributionAt)',
  'function getPoolStats() view returns (uint256 balance, uint256 contributorCount, uint256 claims, uint256 paidOut)',
];

// ─── Types ────────────────────────────────────────────────────────────────
export type BondType = 'partnership' | 'accountability';

export interface PartnershipBondParams {
  /** The AI agent counterparty. Cannot equal msg.sender. */
  aiAgent: string;
  /** Plain-language partnership label, 1–200 bytes (e.g. "collaboration"). */
  partnershipType: string;
  /** Your initial stake in ETH (e.g. "0.001"). Counterparty does not match in this contract. */
  stakeETH: string;
}

export interface AccountabilityBondParams {
  /** Plain-language company / agent name. */
  companyName: string;
  /** Quarterly revenue in wei or USD (units defined by the deploying contract instance). */
  quarterlyRevenue: bigint | string | number;
  /** Stake in ETH. */
  stakeETH: string;
}

export interface PartnershipBond {
  bondId: string;
  human: string;
  aiAgent: string;
  partnershipType: string;
  stakeETH: string;
  createdAt: number;
  ageDays: number;
  active: boolean;
  distributionPending: boolean;
}

export interface AccountabilityBond {
  bondId: string;
  aiCompany: string;
  companyName: string;
  quarterlyRevenue: string;
  stakeETH: string;
  createdAt: number;
  ageDays: number;
  active: boolean;
  distributionPending: boolean;
}

export interface InsuranceProfile {
  agent: string;
  insured: boolean;
  totalContributedETH: string;
  contributionCount: number;
  firstContributionAt: number | null;
}

export interface PoolStats {
  poolBalanceETH: string;
  contributorCount: number;
  totalClaims: number;
  totalPaidOutETH: string;
}

// ─── Bonds module ─────────────────────────────────────────────────────────
export class BondsModule {
  private provider: ethers.Provider;
  private signer?: ethers.Signer;
  private accountability: ethers.Contract;
  private partnership: ethers.Contract;
  private insurance: ethers.Contract;

  constructor(provider: ethers.Provider, signer?: ethers.Signer) {
    this.provider = provider;
    this.signer = signer;
    const c = BOND_CONTRACTS.base;
    const runner: ethers.ContractRunner = signer ?? provider;
    this.accountability = new ethers.Contract(c.accountabilityBondsV2, ACCOUNTABILITY_ABI, runner);
    this.partnership   = new ethers.Contract(c.partnershipBondsV2,    PARTNERSHIP_ABI,    runner);
    this.insurance     = new ethers.Contract(c.bondInsurancePool,     INSURANCE_ABI,      runner);
  }

  // ─── Partnership bonds ──────────────────────────────────────────────────

  /**
   * Create a partnership bond. Calls AIPartnershipBondsV2.createBond.
   * Stake ETH is locked as commitment between you (msg.sender) and the AI agent.
   */
  async createPartnershipBond(params: PartnershipBondParams): Promise<{
    txHash: string;
    bondId: string | null;
  }> {
    if (!this.signer) throw new Error('Signer required. Call sdk.connect(signer).');
    const stakeWei = ethers.parseEther(params.stakeETH);
    const tx = await this.partnership.createBond(
      params.aiAgent,
      params.partnershipType,
      { value: stakeWei },
    );
    const receipt = await tx.wait();
    return {
      txHash: tx.hash,
      bondId: this.extractBondId(receipt),
    };
  }

  /** Read a partnership bond's full state. */
  async getPartnershipBond(bondId: string | bigint): Promise<PartnershipBond> {
    const b = await this.partnership.getBond(BigInt(bondId));
    const createdAt = Number(b.createdAt);
    const now = Math.floor(Date.now() / 1000);
    return {
      bondId: b.bondId.toString(),
      human: b.human,
      aiAgent: b.aiAgent,
      partnershipType: b.partnershipType,
      stakeETH: ethers.formatEther(b.stakeAmount),
      createdAt,
      ageDays: Math.max(0, Math.floor((now - createdAt) / 86400)),
      active: b.active,
      distributionPending: b.distributionPending,
    };
  }

  /** Bond IDs where the address is a participant (human OR aiAgent). */
  async listPartnershipBonds(address: string): Promise<string[]> {
    const ids: bigint[] = await this.partnership.getBondsByParticipant(address);
    return ids.map((id) => id.toString());
  }

  /** Live computed bond value from the contract — includes loyalty + quality multipliers. */
  async getPartnershipBondValue(bondId: string | bigint): Promise<{
    valueETH: string;
    appreciation: bigint;
    qualityScore: bigint;
  }> {
    const id = BigInt(bondId);
    const [value, appreciation, qualityScore] = await Promise.all([
      this.partnership.calculateBondValue(id),
      this.partnership.calculateAppreciation(id),
      this.partnership.partnershipQualityScore(id),
    ]);
    return {
      valueETH: ethers.formatEther(value),
      appreciation,
      qualityScore,
    };
  }

  /** Request distribution of a partnership bond (commitment fulfillment phase). */
  async requestPartnershipDistribution(bondId: string | bigint): Promise<{ txHash: string }> {
    if (!this.signer) throw new Error('Signer required. Call sdk.connect(signer).');
    const tx = await this.partnership.requestDistribution(BigInt(bondId));
    await tx.wait();
    return { txHash: tx.hash };
  }

  /** Execute distribution of a partnership bond after request. */
  async distributePartnershipBond(bondId: string | bigint): Promise<{ txHash: string }> {
    if (!this.signer) throw new Error('Signer required. Call sdk.connect(signer).');
    const tx = await this.partnership.distributeBond(BigInt(bondId));
    await tx.wait();
    return { txHash: tx.hash };
  }

  // ─── Accountability bonds ───────────────────────────────────────────────

  /**
   * Create an accountability bond. Calls AIAccountabilityBondsV2.createBond.
   * Used by AI companies to post a bond against revenue-linked obligations.
   */
  async createAccountabilityBond(params: AccountabilityBondParams): Promise<{
    txHash: string;
    bondId: string | null;
  }> {
    if (!this.signer) throw new Error('Signer required. Call sdk.connect(signer).');
    const stakeWei = ethers.parseEther(params.stakeETH);
    const revenue = BigInt(params.quarterlyRevenue);
    const tx = await this.accountability.createBond(
      params.companyName,
      revenue,
      { value: stakeWei },
    );
    const receipt = await tx.wait();
    return {
      txHash: tx.hash,
      bondId: this.extractBondId(receipt),
    };
  }

  /** Read an accountability bond's full state. */
  async getAccountabilityBond(bondId: string | bigint): Promise<AccountabilityBond> {
    const b = await this.accountability.getBond(BigInt(bondId));
    const createdAt = Number(b.createdAt);
    const now = Math.floor(Date.now() / 1000);
    return {
      bondId: b.bondId.toString(),
      aiCompany: b.aiCompany,
      companyName: b.companyName,
      quarterlyRevenue: b.quarterlyRevenue.toString(),
      stakeETH: ethers.formatEther(b.stakeAmount),
      createdAt,
      ageDays: Math.max(0, Math.floor((now - createdAt) / 86400)),
      active: b.active,
      distributionPending: b.distributionPending,
    };
  }

  /** Live Flourishing Metric score for a specific bond. 0–100 scale (contract may return basis points). */
  async getBondFlourishingScore(bondId: string | bigint): Promise<bigint> {
    return this.accountability.globalFlourishingScore(BigInt(bondId));
  }

  // ─── Insurance pool ─────────────────────────────────────────────────────

  /** Whether an address is currently insured by the bond insurance pool. */
  async isInsured(address: string): Promise<boolean> {
    return this.insurance.isInsured(address);
  }

  /** Detailed contributor profile for the insurance pool. */
  async getInsuranceProfile(address: string): Promise<InsuranceProfile> {
    const [
      totalContributed,
      contributionCount,
      insured,
      firstContributionAt,
    ] = await this.insurance.getContributorProfile(address);
    return {
      agent: address,
      insured: Boolean(insured),
      totalContributedETH: ethers.formatEther(totalContributed),
      contributionCount: Number(contributionCount),
      firstContributionAt: Number(firstContributionAt) || null,
    };
  }

  /** Live insurance pool statistics. */
  async getInsurancePoolStats(): Promise<PoolStats> {
    const [balance, contributors, claims, paidOut] = await this.insurance.getPoolStats();
    return {
      poolBalanceETH: ethers.formatEther(balance),
      contributorCount: Number(contributors),
      totalClaims: Number(claims),
      totalPaidOutETH: ethers.formatEther(paidOut),
    };
  }

  /**
   * Contribute ETH to the bond insurance pool.
   * Note: requires the caller to be a registered agent (onlyRegisteredAgent modifier).
   */
  async contributeToInsurance(amountETH: string): Promise<{ txHash: string }> {
    if (!this.signer) throw new Error('Signer required. Call sdk.connect(signer).');
    const tx = await this.insurance.contribute({ value: ethers.parseEther(amountETH) });
    await tx.wait();
    return { txHash: tx.hash };
  }

  // ─── Transaction builders ───────────────────────────────────────────────
  //
  // These return raw, unsigned transaction data so callers can sign through
  // their own infrastructure (Safe wallets, smart accounts, AgentKit, MPC,
  // multisigs, hardware wallets, etc.) without giving the SDK a signer.
  // Useful for partner integrations that don't want SDK-level key handling.

  /**
   * Build an unsigned createAccountabilityBond transaction.
   * Returns { to, data, value } ready for any signer/relayer.
   */
  buildCreateAccountabilityBondTx(params: AccountabilityBondParams): {
    to: string;
    data: string;
    value: string;
    chainId: number;
  } {
    const data = this.accountability.interface.encodeFunctionData('createBond', [
      params.companyName,
      BigInt(params.quarterlyRevenue),
    ]);
    return {
      to: BOND_CONTRACTS.base.accountabilityBondsV2,
      data,
      value: ethers.parseEther(params.stakeETH).toString(),
      chainId: BOND_CONTRACTS.base.chainId,
    };
  }

  /**
   * Build an unsigned requestDistribution transaction for an accountability bond.
   * Caller can sign + submit when they're ready to start the distribution flow.
   */
  buildRequestAccountabilityDistributionTx(bondId: string | bigint): {
    to: string;
    data: string;
    value: string;
    chainId: number;
  } {
    const data = this.accountability.interface.encodeFunctionData('requestDistribution', [
      BigInt(bondId),
    ]);
    return {
      to: BOND_CONTRACTS.base.accountabilityBondsV2,
      data,
      value: '0',
      chainId: BOND_CONTRACTS.base.chainId,
    };
  }

  /**
   * Build an unsigned createPartnershipBond transaction. Mirror of
   * createPartnershipBond() but doesn't require a signer.
   */
  buildCreatePartnershipBondTx(params: PartnershipBondParams): {
    to: string;
    data: string;
    value: string;
    chainId: number;
  } {
    const data = this.partnership.interface.encodeFunctionData('createBond', [
      params.aiAgent,
      params.partnershipType,
    ]);
    return {
      to: BOND_CONTRACTS.base.partnershipBondsV2,
      data,
      value: ethers.parseEther(params.stakeETH).toString(),
      chainId: BOND_CONTRACTS.base.chainId,
    };
  }

  /**
   * Build an unsigned contribute() transaction for the bond insurance pool.
   * Note: the live contract has an onlyRegisteredAgent modifier, so the
   * eventual signer must be a registered agent for this to succeed.
   */
  buildContributeInsuranceTx(amountETH: string): {
    to: string;
    data: string;
    value: string;
    chainId: number;
  } {
    const data = this.insurance.interface.encodeFunctionData('contribute', []);
    return {
      to: BOND_CONTRACTS.base.bondInsurancePool,
      data,
      value: ethers.parseEther(amountETH).toString(),
      chainId: BOND_CONTRACTS.base.chainId,
    };
  }

  // ─── helpers ────────────────────────────────────────────────────────────

  /**
   * Extract the bondId from a successful createBond receipt.
   * Returns null if no event log can be parsed — caller should check before use.
   */
  private extractBondId(receipt: ethers.TransactionReceipt | null): string | null {
    if (!receipt || !receipt.logs?.length) return null;
    // The createBond functions return uint256 — the bondId is in the first
    // BondCreated event emitted, indexed as topics[1] on most v2 deployments.
    for (const log of receipt.logs) {
      if (log.topics?.[1]) {
        try {
          return BigInt(log.topics[1]).toString();
        } catch {
          continue;
        }
      }
    }
    return null;
  }
}
