// Smart Clearing Fabric Service
// Netting, multi-legged transactions, DBIS master posting

import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import { ledgerService } from '@/core/ledger/ledger.service';
import prisma from '@/shared/database/prisma';
import { logger } from '@/infrastructure/monitoring/logger';

export interface ClearingTransaction {
  sourceBankId: string;
  destinationBankId: string;
  amount: number;
  currencyCode: string;
  assetType: string;
  referenceId: string;
}

export interface NettingResult {
  nettedTransactions: number;
  netAmount: number;
  remainingTransactions: ClearingTransaction[];
}

export class SmartClearingService {
  /**
   * Net bilateral obligations
   */
  async netBilateralObligations(
    bank1Id: string,
    bank2Id: string,
    currencyCode: string
  ): Promise<NettingResult> {
    // Get pending transactions between banks
    const transactions = await prisma.ledger_entries.findMany({
      where: {
        status: 'pending',
        currencyCode,
        OR: [
          {
            debitAccountId: {
              sovereignBankId: bank1Id,
            },
            creditAccountId: {
              sovereignBankId: bank2Id,
            },
          },
          {
            debitAccountId: {
              sovereignBankId: bank2Id,
            },
            creditAccountId: {
              sovereignBankId: bank1Id,
            },
          },
        ],
      },
      include: {
        debitAccount: true,
        creditAccount: true,
      },
    });

    let netAmount = 0;
    const nettedTransactions: string[] = [];

    for (const tx of transactions) {
      const amount = parseFloat(tx.amount.toString());
      if (tx.debitAccount.sovereignBankId === bank1Id) {
        // Bank1 owes Bank2
        netAmount -= amount;
      } else {
        // Bank2 owes Bank1
        netAmount += amount;
      }
      nettedTransactions.push(tx.id);
    }

    // Create net settlement if net amount is significant
    if (Math.abs(netAmount) > 0.01) {
      const netTransaction: ClearingTransaction = {
        sourceBankId: netAmount > 0 ? bank2Id : bank1Id,
        destinationBankId: netAmount > 0 ? bank1Id : bank2Id,
        amount: Math.abs(netAmount),
        currencyCode,
        assetType: 'fiat',
        referenceId: uuidv4(),
      };

      return {
        nettedTransactions: transactions.length,
        netAmount: Math.abs(netAmount),
        remainingTransactions: [netTransaction],
      };
    }

    return {
      nettedTransactions: transactions.length,
      netAmount: 0,
      remainingTransactions: [],
    };
  }

  /**
   * Execute multi-legged transaction
   */
  async executeMultiLeggedTransaction(
    legs: Array<{
      sourceBankId: string;
      destinationBankId: string;
      amount: number;
      currencyCode: string;
      assetType: string;
    }>
  ): Promise<{ success: boolean; entryIds: string[] }> {
    const entryIds: string[] = [];
    const transactionId = uuidv4();

    try {
      // Execute all legs atomically
      for (const leg of legs) {
        // Get accounts
        const sourceAccounts = await prisma.bank_accounts.findMany({
          where: {
            sovereignBankId: leg.sourceBankId,
            currencyCode: leg.currencyCode,
            assetType: leg.assetType,
            status: 'active',
          },
          take: 1,
        });

        const destAccounts = await prisma.bank_accounts.findMany({
          where: {
            sovereignBankId: leg.destinationBankId,
            currencyCode: leg.currencyCode,
            assetType: leg.assetType,
            status: 'active',
          },
          take: 1,
        });

        if (sourceAccounts.length === 0 || destAccounts.length === 0) {
          throw new Error(`Accounts not found for leg: ${leg.sourceBankId} -> ${leg.destinationBankId}`);
        }

        // Post ledger entry
        const result = await ledgerService.postDoubleEntry(
          'Master',
          sourceAccounts[0].id,
          destAccounts[0].id,
          leg.amount.toString(),
          leg.currencyCode,
          leg.assetType as any,
          'Type_A',
          transactionId
        );

        entryIds.push(...result.entryIds);
      }

      return { success: true, entryIds };
    } catch (error) {
      // Rollback on error
      for (const entryId of entryIds) {
        try {
          await ledgerService.reverseEntry(entryId);
        } catch (rollbackError) {
          logger.error('Failed to rollback entry', {
            entryId,
            error: rollbackError instanceof Error ? rollbackError.message : 'Unknown error',
          });
        }
      }
      throw error;
    }
  }

  /**
   * Post to DBIS master ledger
   */
  async postToDBISMaster(
    transactionId: string,
    entries: Array<{
      debitAccountId: string;
      creditAccountId: string;
      amount: number;
      currencyCode: string;
      assetType: string;
    }>
  ): Promise<{ success: boolean; blockHash: string }> {
    const entryIds: string[] = [];

    try {
      for (const entry of entries) {
        const result = await ledgerService.postDoubleEntry(
          'Master',
          entry.debitAccountId,
          entry.creditAccountId,
          entry.amount.toString(),
          entry.currencyCode,
          entry.assetType as any,
          'Type_A',
          transactionId
        );

        entryIds.push(...result.entryIds[0]);
      }

      // Get block hash from last entry
      const lastEntry = await prisma.ledger_entries.findUnique({
        where: { id: entryIds[entryIds.length - 1] },
      });

      return {
        success: true,
        blockHash: lastEntry?.blockHash || '',
      };
    } catch (error) {
      throw error;
    }
  }

  /**
   * Clear and settle batch of transactions
   */
  async clearAndSettleBatch(transactionIds: string[]): Promise<{
    cleared: number;
    failed: number;
    results: Array<{ transactionId: string; status: string; error?: string }>;
  }> {
    const results: Array<{ transactionId: string; status: string; error?: string }> = [];
    let cleared = 0;
    let failed = 0;

    for (const transactionId of transactionIds) {
      try {
        // Get transaction entries
        const entries = await prisma.ledger_entries.findMany({
          where: {
            referenceId: transactionId,
            status: 'pending',
          },
        });

        if (entries.length === 0) {
          results.push({
            transactionId,
            status: 'not_found',
            error: 'No pending entries found',
          });
          failed++;
          continue;
        }

        // Update all entries to settled
        await prisma.ledger_entries.updateMany({
          where: {
            referenceId: transactionId,
            status: 'pending',
          },
          data: {
            status: 'settled',
          },
        });

        results.push({
          transactionId,
          status: 'cleared',
        });
        cleared++;
      } catch (error) {
        results.push({
          transactionId,
          status: 'failed',
          error: error instanceof Error ? error.message : 'Unknown error',
        });
        failed++;
      }
    }

    return { cleared, failed, results };
  }
}

export const smartClearingService = new SmartClearingService();

