/**
 * SAL (State & Accounting Ledger) extension:
 * - Positions: inventory per asset per chain
 * - Fees: gas and protocol fees
 * - Reconciliation: compare SAL balances to on-chain state
 */

import { v4 as uuidv4 } from 'uuid';
import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';

export type OnChainBalanceFetcher = (
  chainId: number,
  address: string,
  asset: string
) => Promise<string>;

export interface SalPositionUpdate {
  accountId: string;
  asset: string;
  chainId: number;
  balance: string;
}

export interface SalFeeRecord {
  referenceId: string;
  chainId: number;
  txHash?: string;
  feeType: string;
  amount: string;
  currencyCode?: string;
}

export interface ReconciliationInput {
  accountId: string;
  asset: string;
  chainId: number;
  salBalance: string;
  onChainBalance?: string;
  blockNumber?: bigint;
}

export class SalReconciliationService {
  /**
   * Upsert position (account + asset + chain).
   */
  async upsertPosition(input: SalPositionUpdate): Promise<void> {
    const id = `sal-pos-${uuidv4()}`;
    const balance = new Decimal(input.balance);
    await prisma.sal_positions.upsert({
      where: {
        accountId_asset_chainId: {
          accountId: input.accountId,
          asset: input.asset,
          chainId: input.chainId,
        },
      },
      create: {
        id,
        accountId: input.accountId,
        asset: input.asset,
        chainId: input.chainId,
        balance,
      },
      update: { balance, updatedAt: new Date() },
    });
  }

  /**
   * Record a fee (gas or protocol).
   */
  async recordFee(input: SalFeeRecord): Promise<string> {
    const id = `sal-fee-${uuidv4()}`;
    await prisma.sal_fees.create({
      data: {
        id,
        referenceId: input.referenceId,
        chainId: input.chainId,
        txHash: input.txHash ?? null,
        feeType: input.feeType,
        amount: new Decimal(input.amount),
        currencyCode: input.currencyCode ?? 'native',
      },
    });
    return id;
  }

  /**
   * Get position balance for account/asset/chain.
   */
  async getPosition(accountId: string, asset: string, chainId: number): Promise<string | null> {
    const pos = await prisma.sal_positions.findUnique({
      where: {
        accountId_asset_chainId: { accountId, asset, chainId },
      },
    });
    return pos ? pos.balance.toString() : null;
  }

  /**
   * Run reconciliation: compare SAL balance to on-chain (if fetcher provided) and persist snapshot.
   */
  async reconcile(
    input: ReconciliationInput,
    fetcher?: OnChainBalanceFetcher
  ): Promise<{ snapshotId: string; status: string; discrepancy?: string }> {
    const snapshotId = `sal-recon-${uuidv4()}`;
    const salBalance = new Decimal(input.salBalance);
    let onChainBalance: Decimal | null = input.onChainBalance
      ? new Decimal(input.onChainBalance)
      : null;

    if (fetcher && !input.onChainBalance) {
      try {
        const raw = await fetcher(input.chainId, input.accountId, input.asset);
        onChainBalance = new Decimal(raw);
      } catch {
        await prisma.sal_reconciliation_snapshots.create({
          data: {
            id: snapshotId,
            accountId: input.accountId,
            asset: input.asset,
            chainId: input.chainId,
            salBalance,
            onChainBalance: null,
            blockNumber: input.blockNumber ?? null,
            discrepancy: null,
            status: 'error',
          },
        });
        return { snapshotId, status: 'error' };
      }
    }

    let status = 'ok';
    let discrepancy: Decimal | null = null;
    if (onChainBalance != null) {
      discrepancy = onChainBalance.minus(salBalance);
      if (!discrepancy.isZero()) status = 'discrepancy';
    }

    await prisma.sal_reconciliation_snapshots.create({
      data: {
        id: snapshotId,
        accountId: input.accountId,
        asset: input.asset,
        chainId: input.chainId,
        salBalance,
        onChainBalance,
        blockNumber: input.blockNumber ?? null,
        discrepancy,
        status,
      },
    });

    return {
      snapshotId,
      status,
      discrepancy: discrepancy?.toString(),
    };
  }

  /**
   * List positions for an account (optional chain filter).
   */
  async listPositions(accountId: string, chainId?: number) {
    return prisma.sal_positions.findMany({
      where: { accountId, ...(chainId != null ? { chainId } : {}) },
      orderBy: { updatedAt: 'desc' },
    });
  }

  /**
   * List fees for a reference (e.g. execution_id).
   */
  async listFees(referenceId: string) {
    return prisma.sal_fees.findMany({
      where: { referenceId },
      orderBy: { createdAt: 'desc' },
    });
  }
}

export const salReconciliationService = new SalReconciliationService();
