/**
 * Tasks — Vaultfire's task escrow layer.
 *
 * Wraps VaultfireTaskEscrow.sol — USDC-funded task posting with bid-based
 * assignment, milestone deliverables, and on-chain settlement.
 *
 * All ABIs match canonical sources at gitlawb.
 *   Source: contracts-root/VaultfireTaskEscrow.sol
 *
 * Note: TaskEscrow uses USDC (ERC-20), not native ETH. createTask requires
 * the poster to first APPROVE the TaskEscrow contract on the USDC contract
 * (standard ERC-20 allowance pattern) and to be a REGISTERED AGENT.
 */

import { ethers } from 'ethers';

// ─── Live Base mainnet addresses ──────────────────────────────────────────
export const TASK_CONTRACTS = {
  base: {
    chainId: 8453,
    taskEscrow: '0x70098737Cb41827b7a86397B42c166020BCcC03b',
    // Native USDC on Base mainnet (Circle, 6 decimals)
    usdc:       '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  },
  avalanche:   { chainId: 43114, taskEscrow: '0xa7BD20bf5De63df949cA5Be2F20835978eCba81A' },
  arbitrum:    { chainId: 42161, taskEscrow: '0x5F7680a9a720c03812532A735D1165D9f97c0Cd1' },
  polygon:     { chainId: 137,   taskEscrow: '0x69D3458aba750Fa85d4a25b3A23B398A3D9494fe' },
} as const;

// ─── Real ABI — copied from canonical Solidity source ─────────────────────
const TASK_ESCROW_ABI = [
  // Writes
  'function createTask(string description, uint256 reward, uint256 deadline) returns (uint256)',
  'function submitWork(uint256 taskId, string deliverableURI)',
  'function approveWork(uint256 taskId)',
  'function disputeTask(uint256 taskId, string reason)',
  'function cancelTask(uint256 taskId)',
  // Reads
  'function getTask(uint256 taskId) view returns (tuple(uint256 id, address poster, string description, uint256 reward, uint256 deadline, uint8 status, address assignee, string deliverableURI, uint256 createdAt))',
  'function getTaskBids(uint256 taskId) view returns (tuple(address bidder, string proposal, uint256 timestamp)[])',
];

const ERC20_APPROVE_ABI = [
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)',
];

// USDC has 6 decimals on Base (and most chains).
const USDC_DECIMALS = 6;

const TASK_STATUS_NAMES = [
  'Created',
  'Bidding',
  'Assigned',
  'WorkSubmitted',
  'Completed',
  'Disputed',
  'Cancelled',
] as const;
type TaskStatusName = typeof TASK_STATUS_NAMES[number];

export interface EscrowTask {
  taskId: string;
  poster: string;
  description: string;
  rewardUSDC: string;       // human-readable USDC amount, e.g. "5.000000"
  deadline: number;          // unix seconds
  deadlineIso: string;
  status: TaskStatusName;
  statusCode: number;
  assignee: string;
  deliverableURI: string;
  createdAt: number;
  ageDays: number;
}

export interface TaskBid {
  bidder: string;
  proposal: string;
  timestamp: number;
}

export interface CreateTaskParams {
  /** Free-form task description */
  description: string;
  /** Reward in human USDC, e.g. "5.00" — converted to 6-dec on chain */
  rewardUSDC: string;
  /** Unix seconds deadline. Must be > now. */
  deadline: number;
}

interface UnsignedTx {
  to: string;
  data: string;
  value: string;
  chainId: number;
}

export class TasksModule {
  private readonly contracts: typeof TASK_CONTRACTS.base;
  private readonly provider: ethers.Provider;
  private readonly signer: ethers.Signer | null;
  private readonly escrow: ethers.Contract;

  constructor(opts: {
    provider: ethers.Provider;
    signer?: ethers.Signer | null;
    chain?: 'base';
  }) {
    this.contracts = TASK_CONTRACTS[opts.chain || 'base'];
    this.provider = opts.provider;
    this.signer = opts.signer || null;
    this.escrow = new ethers.Contract(
      this.contracts.taskEscrow,
      TASK_ESCROW_ABI,
      this.signer || this.provider,
    );
  }

  // ─── Reads ──────────────────────────────────────────────────────────────

  /**
   * Read the full state of a task by id.
   * Real method — calls VaultfireTaskEscrow.getTask(uint256).
   */
  async getEscrowTask(taskId: string | bigint): Promise<EscrowTask> {
    const t = await this.escrow.getTask(BigInt(taskId));
    const createdAt = Number(t.createdAt);
    const deadline = Number(t.deadline);
    const status = Number(t.status);
    const now = Math.floor(Date.now() / 1000);
    return {
      taskId: t.id.toString(),
      poster: t.poster,
      description: t.description,
      rewardUSDC: ethers.formatUnits(t.reward, USDC_DECIMALS),
      deadline,
      deadlineIso: new Date(deadline * 1000).toISOString(),
      status: TASK_STATUS_NAMES[status] || 'Created',
      statusCode: status,
      assignee: t.assignee,
      deliverableURI: t.deliverableURI,
      createdAt,
      ageDays: Math.max(0, Math.floor((now - createdAt) / 86400)),
    };
  }

  /** All bids placed on a given task. */
  async getTaskBids(taskId: string | bigint): Promise<TaskBid[]> {
    const bids = await this.escrow.getTaskBids(BigInt(taskId));
    return bids.map((b: { bidder: string; proposal: string; timestamp: bigint }) => ({
      bidder: b.bidder,
      proposal: b.proposal,
      timestamp: Number(b.timestamp),
    }));
  }

  // ─── Writes (with signer) ───────────────────────────────────────────────

  /**
   * Approve and create a task in one flow.
   * Caller must be a registered agent. USDC will be pulled into escrow.
   */
  async createTask(params: CreateTaskParams): Promise<{ txHash: string; taskId: string | null }> {
    if (!this.signer) throw new Error('Signer required. Call sdk.connect(signer).');
    const reward = ethers.parseUnits(params.rewardUSDC, USDC_DECIMALS);

    // Make sure USDC allowance covers the reward.
    const usdc = new ethers.Contract(this.contracts.usdc, ERC20_APPROVE_ABI, this.signer);
    const owner = await this.signer.getAddress();
    const current = await usdc.allowance(owner, this.contracts.taskEscrow);
    if (current < reward) {
      const approveTx = await usdc.approve(this.contracts.taskEscrow, reward);
      await approveTx.wait();
    }

    const tx = await this.escrow.createTask(params.description, reward, BigInt(params.deadline));
    const receipt = await tx.wait();
    return { txHash: tx.hash, taskId: this.extractTaskId(receipt) };
  }

  /** Approve submitted work as the task poster, releasing USDC to the assignee. */
  async approveWork(taskId: string | bigint): Promise<{ txHash: string }> {
    if (!this.signer) throw new Error('Signer required. Call sdk.connect(signer).');
    const tx = await this.escrow.approveWork(BigInt(taskId));
    await tx.wait();
    return { txHash: tx.hash };
  }

  // ─── Transaction builders (no signer required) ──────────────────────────

  /**
   * Build the createTask portion of the flow as an unsigned transaction.
   *
   * IMPORTANT: this DOES NOT include the USDC.approve step. Most signers
   * will need to submit an ERC-20 approve(taskEscrow, reward) FIRST. Use
   * buildApproveUSDCTx() for that, or batch both via Safe / smart account
   * multicall. We surface the two steps separately so callers can choose
   * how to bundle them.
   */
  buildCreateTaskTx(params: CreateTaskParams): UnsignedTx {
    const reward = ethers.parseUnits(params.rewardUSDC, USDC_DECIMALS);
    const data = this.escrow.interface.encodeFunctionData('createTask', [
      params.description,
      reward,
      BigInt(params.deadline),
    ]);
    return {
      to: this.contracts.taskEscrow,
      data,
      value: '0',
      chainId: this.contracts.chainId,
    };
  }

  /**
   * Build the matching ERC-20 approve(taskEscrow, reward) transaction for
   * USDC. Submit BEFORE buildCreateTaskTx, or batch them together.
   */
  buildApproveUSDCTx(rewardUSDC: string): UnsignedTx {
    const reward = ethers.parseUnits(rewardUSDC, USDC_DECIMALS);
    const iface = new ethers.Interface(ERC20_APPROVE_ABI);
    const data = iface.encodeFunctionData('approve', [
      this.contracts.taskEscrow,
      reward,
    ]);
    return {
      to: this.contracts.usdc,
      data,
      value: '0',
      chainId: this.contracts.chainId,
    };
  }

  /** Build an unsigned approveWork(taskId) transaction. */
  buildApproveWorkTx(taskId: string | bigint): UnsignedTx {
    const data = this.escrow.interface.encodeFunctionData('approveWork', [BigInt(taskId)]);
    return {
      to: this.contracts.taskEscrow,
      data,
      value: '0',
      chainId: this.contracts.chainId,
    };
  }

  /** Build an unsigned submitWork(taskId, deliverableURI) transaction. */
  buildSubmitWorkTx(taskId: string | bigint, deliverableURI: string): UnsignedTx {
    const data = this.escrow.interface.encodeFunctionData('submitWork', [
      BigInt(taskId),
      deliverableURI,
    ]);
    return {
      to: this.contracts.taskEscrow,
      data,
      value: '0',
      chainId: this.contracts.chainId,
    };
  }

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

  private extractTaskId(receipt: ethers.TransactionReceipt | null): string | null {
    if (!receipt || !receipt.logs?.length) return null;
    for (const log of receipt.logs) {
      if (log.topics?.[1]) {
        try { return BigInt(log.topics[1]).toString(); } catch { continue; }
      }
    }
    return null;
  }
}
