// Atomic Settlement Service
// Dual-commitment protocol, instant finality

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


export interface AtomicSettlementRequest {
  sourceBankId: string;
  destinationBankId: string;
  amount: number;
  currencyCode: string;
  assetType: string;
  transactionId?: string;
}

export interface AtomicSettlementResult {
  settlementId: string;
  status: string;
  sovereignLedgerHash: string;
  dbisLedgerHash: string;
  settlementTime: number;
}

export class AtomicSettlementService {
  /**
   * Execute atomic settlement
   */
  async executeAtomicSettlement(
    request: AtomicSettlementRequest
  ): Promise<AtomicSettlementResult> {
    const startTime = Date.now();
    const settlementId = uuidv4();
    const transactionId = request.transactionId || uuidv4();

    try {
      // Step 1: Prepare dual-commitment
      const commitment = await this.prepareDualCommitment(request, settlementId);

      // Step 2: Commit to sovereign ledgers
      const sovereignHash = await this.commitToSovereignLedger(
        request.sourceBankId,
        request,
        commitment
      );

      // Step 3: Commit to DBIS master ledger
      const dbisHash = await this.commitToDBISMaster(request, commitment);

      // Step 4: Verify both commitments
      const verified = await this.verifyDualCommitment(sovereignHash, dbisHash);
      if (!verified) {
        throw new Error('Dual commitment verification failed');
      }

      // Step 5: Finalize settlement
      const settlementTime = Date.now() - startTime;

      const settlement = await prisma.atomicSettlement.create({
        data: {
          id: uuidv4(),
          settlementId,
          transactionId,
          sourceBankId: request.sourceBankId,
          destinationBankId: request.destinationBankId,
          amount: new Decimal(request.amount),
          currencyCode: request.currencyCode,
          assetType: request.assetType,
          settlementMode: 'atomic',
          dualLedgerCommit: true,
          sovereignLedgerHash: sovereignHash,
          dbisLedgerHash: dbisHash,
          settlementTime,
          status: 'settled',
          committedAt: new Date(),
          settledAt: new Date(),
        },
      });

      return {
        settlementId,
        status: 'settled',
        sovereignLedgerHash: sovereignHash,
        dbisLedgerHash: dbisHash,
        settlementTime,
      };
    } catch (error) {
      // Create failed settlement record
      await prisma.atomicSettlement.create({
        data: {
          id: uuidv4(),
          settlementId,
          transactionId: request.transactionId || uuidv4(),
          sourceBankId: request.sourceBankId,
          destinationBankId: request.destinationBankId,
          amount: new Decimal(request.amount),
          currencyCode: request.currencyCode,
          assetType: request.assetType,
          settlementMode: 'atomic',
          dualLedgerCommit: false,
          status: 'failed',
        },
      });

      throw error;
    }
  }

  /**
   * Prepare dual-commitment
   */
  private async prepareDualCommitment(
    request: AtomicSettlementRequest,
    settlementId: string
  ): Promise<string> {
    const commitmentData = {
      settlementId,
      sourceBankId: request.sourceBankId,
      destinationBankId: request.destinationBankId,
      amount: request.amount,
      currencyCode: request.currencyCode,
      assetType: request.assetType,
      timestamp: new Date().toISOString(),
    };

    // Create commitment hash
    const commitmentString = JSON.stringify(commitmentData);
    const commitmentHash = createHash('sha3-256').update(commitmentString).digest('hex');

    return commitmentHash;
  }

  /**
   * Commit to sovereign ledger
   */
  private async commitToSovereignLedger(
    sovereignBankId: string,
    request: AtomicSettlementRequest,
    commitment: string
  ): Promise<string> {
    // Get accounts
    const sourceAccounts = await prisma.bankAccount.findMany({
      where: {
        sovereignBankId: request.sourceBankId,
        currencyCode: request.currencyCode,
        assetType: request.assetType,
        status: 'active',
      },
      take: 1,
    });

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

    if (sourceAccounts.length === 0 || destAccounts.length === 0) {
      throw new Error('Accounts not found for settlement');
    }

    // Post to sovereign ledger
    const result = await ledgerService.postDoubleEntry(
      `Sovereign_${sovereignBankId}`,
      sourceAccounts[0].id,
      destAccounts[0].id,
      request.amount.toString(),
      request.currencyCode,
      request.assetType as any,
      'Type_A',
      uuidv4(),
      undefined,
      { commitment }
    );

    // Get block hash
    const entry = await prisma.ledgerEntry.findUnique({
      where: { id: result.entryIds[0] },
    });

    return entry?.blockHash || '';
  }

  /**
   * Commit to DBIS master ledger
   */
  private async commitToDBISMaster(
    request: AtomicSettlementRequest,
    commitment: string
  ): Promise<string> {
    // Get accounts
    const sourceAccounts = await prisma.bankAccount.findMany({
      where: {
        sovereignBankId: request.sourceBankId,
        currencyCode: request.currencyCode,
        assetType: request.assetType,
        status: 'active',
      },
      take: 1,
    });

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

    if (sourceAccounts.length === 0 || destAccounts.length === 0) {
      throw new Error('Accounts not found for DBIS master posting');
    }

    // Post to DBIS master ledger
    const result = await ledgerService.postDoubleEntry(
      'Master',
      sourceAccounts[0].id,
      destAccounts[0].id,
      request.amount.toString(),
      request.currencyCode,
      request.assetType as any,
      'Type_A',
      uuidv4(),
      undefined,
      { commitment }
    );

    // Get block hash
    const entry = await prisma.ledgerEntry.findUnique({
      where: { id: result.entryIds[0] },
    });

    return entry?.blockHash || '';
  }

  /**
   * Verify dual commitment
   */
  private async verifyDualCommitment(
    sovereignHash: string,
    dbisHash: string
  ): Promise<boolean> {
    // Both hashes should exist and be non-empty
    if (!sovereignHash || !dbisHash) {
      return false;
    }

    // Verify entries exist
    const sovereignEntry = await prisma.ledgerEntry.findFirst({
      where: { blockHash: sovereignHash },
    });

    const dbisEntry = await prisma.ledgerEntry.findFirst({
      where: { blockHash: dbisHash },
    });

    return !!(sovereignEntry && dbisEntry);
  }

  /**
   * Get settlement by ID
   */
  async getSettlement(settlementId: string) {
    return await prisma.atomicSettlement.findUnique({
      where: { settlementId },
    });
  }

  /**
   * Get settlements for a bank
   */
  async getSettlementsForBank(sovereignBankId: string, limit: number = 100) {
    return await prisma.atomicSettlement.findMany({
      where: {
        OR: [
          { sourceBankId: sovereignBankId },
          { destinationBankId: sovereignBankId },
        ],
      },
      orderBy: { createdAt: 'desc' },
      take: limit,
    });
  }

  /**
   * Check if settlement is atomic (<100ms)
   */
  async isSettlementAtomic(settlementId: string): Promise<boolean> {
    const settlement = await prisma.atomicSettlement.findUnique({
      where: { settlementId },
    });

    if (!settlement || !settlement.settlementTime) {
      return false;
    }

    return settlement.settlementTime < 100;
  }
}

export const atomicSettlementService = new AtomicSettlementService();

