// Account Management Engine

import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';
import { AccountType, AssetType, BankAccount } from '@/shared/types';
import { DbisError, ErrorCode } from '@/shared/types';
import { v4 as uuidv4 } from 'uuid';

export class AccountService {
  /**
   * Create a new bank account
   */
  async createAccount(
    sovereignBankId: string,
    accountType: AccountType,
    currencyCode: string,
    assetType: AssetType = AssetType.FIAT,
    reserveRequirement?: string
  ): Promise<BankAccount> {
    const accountNumber = this.generateAccountNumber(sovereignBankId, accountType);

    const account = await prisma.bank_accounts.create({
      data: {
        id: uuidv4(),
        accountNumber,
        sovereignBankId,
        accountType,
        currencyCode,
        assetType,
        balance: new Decimal(0),
        availableBalance: new Decimal(0),
        reservedBalance: new Decimal(0),
        reserveRequirement: reserveRequirement ? new Decimal(reserveRequirement) : null,
        status: 'active',
        updatedAt: new Date(),
      },
    });

    return this.mapToBankAccount(account);
  }

  /**
   * Get account by ID
   */
  async getAccount(accountId: string): Promise<BankAccount | null> {
    const account = await prisma.bank_accounts.findUnique({
      where: { id: accountId },
    });

    return account ? this.mapToBankAccount(account) : null;
  }

  /**
   * Get account by account number
   */
  async getAccountByNumber(accountNumber: string): Promise<BankAccount | null> {
    const account = await prisma.bank_accounts.findUnique({
      where: { accountNumber },
    });

    return account ? this.mapToBankAccount(account) : null;
  }

  /**
   * Get accounts for a sovereign bank
   */
  async getAccountsBySovereign(
    sovereignBankId: string,
    accountType?: AccountType
  ): Promise<BankAccount[]> {
    const where: any = { sovereignBankId };
    if (accountType) {
      where.accountType = accountType;
    }

    const accounts = await prisma.bank_accounts.findMany({
      where,
    });

    return accounts.map(this.mapToBankAccount);
  }

  /**
   * Calculate account balance
   */
  async calculateBalance(accountId: string): Promise<{
    balance: string;
    availableBalance: string;
    reservedBalance: string;
  }> {
    const account = await prisma.bank_accounts.findUnique({
      where: { id: accountId },
    });

    if (!account) {
      throw new DbisError(ErrorCode.NOT_FOUND, 'Account not found');
    }

    return {
      balance: account.balance.toString(),
      availableBalance: account.availableBalance.toString(),
      reservedBalance: account.reservedBalance.toString(),
    };
  }

  /**
   * Reserve balance
   */
  async reserveBalance(accountId: string, amount: string): Promise<void> {
    const account = await prisma.bank_accounts.findUnique({
      where: { id: accountId },
    });

    if (!account) {
      throw new DbisError(ErrorCode.NOT_FOUND, 'Account not found');
    }

    const amountDecimal = new Decimal(amount);
    if (account.availableBalance.lt(amountDecimal)) {
      throw new DbisError(ErrorCode.VALIDATION_ERROR, 'Insufficient available balance');
    }

    await prisma.bank_accounts.update({
      where: { id: accountId },
      data: {
        availableBalance: account.availableBalance.minus(amountDecimal),
        reservedBalance: account.reservedBalance.plus(amountDecimal),
      },
    });
  }

  /**
   * Release reserved balance
   */
  async releaseReservedBalance(accountId: string, amount: string): Promise<void> {
    const account = await prisma.bank_accounts.findUnique({
      where: { id: accountId },
    });

    if (!account) {
      throw new DbisError(ErrorCode.NOT_FOUND, 'Account not found');
    }

    const amountDecimal = new Decimal(amount);
    if (account.reservedBalance.lt(amountDecimal)) {
      throw new DbisError(ErrorCode.VALIDATION_ERROR, 'Insufficient reserved balance');
    }

    await prisma.bank_accounts.update({
      where: { id: accountId },
      data: {
        availableBalance: account.availableBalance.plus(amountDecimal),
        reservedBalance: account.reservedBalance.minus(amountDecimal),
      },
    });
  }

  /**
   * Check reserve requirements
   */
  async checkReserveRequirements(accountId: string): Promise<boolean> {
    const account = await prisma.bank_accounts.findUnique({
      where: { id: accountId },
    });

    if (!account || !account.reserveRequirement) {
      return true; // No requirement
    }

    const requiredReserve = account.balance.times(account.reserveRequirement);
    const actualReserve = account.reservedBalance;

    return actualReserve.gte(requiredReserve);
  }

  /**
   * Generate account number
   */
  private generateAccountNumber(sovereignBankId: string, accountType: AccountType): string {
    const prefix = accountType.substring(0, 3).toUpperCase();
    const timestamp = Date.now().toString(36).toUpperCase();
    const random = Math.random().toString(36).substring(2, 6).toUpperCase();
    return `${prefix}-${sovereignBankId.substring(0, 4)}-${timestamp}-${random}`;
  }

  /**
   * Map Prisma model to BankAccount type
   */
  private mapToBankAccount(account: any): BankAccount {
    return {
      id: account.id,
      accountNumber: account.accountNumber,
      sovereignBankId: account.sovereignBankId,
      accountType: account.accountType as AccountType,
      currencyCode: account.currencyCode,
      assetType: account.assetType as AssetType,
      balance: account.balance.toString(),
      availableBalance: account.availableBalance.toString(),
      reservedBalance: account.reservedBalance.toString(),
      reserveRequirement: account.reserveRequirement?.toString(),
      status: account.status,
    };
  }
}

export const accountService = new AccountService();

