// GRU Account Service
// GRU Banking Operations Manual - Account Structure (GRA-0 through GRA-6)

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


export type GruAccountClass = 'GRA-0' | 'GRA-1' | 'GRA-2' | 'GRA-3' | 'GRA-4' | 'GRA-5' | 'GRA-6';

export interface CreateGruAccountRequest {
  accountClass: GruAccountClass;
  entityId: string;
  entityType: string;
  initialBalance?: string;
}

export interface GruAccountInfo {
  id: string;
  accountId: string;
  accountClass: string;
  entityId: string;
  entityType: string;
  accountNumber: string;
  balance: string;
  availableBalance: string;
  reservedBalance: string;
  status: string;
  openedAt: Date;
}

export class GruAccountService {
  /**
   * Initialize account classes if they don't exist
   */
  async initializeAccountClasses(): Promise<void> {
    const accountClasses = [
      {
        accountClass: 'GRA-0',
        className: 'DBIS Master Issuance Account',
        entityType: 'dbis',
        purpose: 'Master issuance account for DBIS',
        permissions: {
          canIssue: true,
          canRedeem: true,
          canSettle: true,
          canViewAll: true,
        },
      },
      {
        accountClass: 'GRA-1',
        className: 'Sovereign Reserve Account',
        entityType: 'scb',
        purpose: 'Sovereign reserve accounts for SCBs',
        permissions: {
          canIssue: false,
          canRedeem: true,
          canSettle: true,
          canViewAll: false,
        },
      },
      {
        accountClass: 'GRA-2',
        className: 'Supranational Reserve Account',
        entityType: 'supranational',
        purpose: 'Regional reserves for supranational entities',
        permissions: {
          canIssue: false,
          canRedeem: true,
          canSettle: true,
          canViewAll: false,
        },
      },
      {
        accountClass: 'GRA-3',
        className: 'Tier-1 Bank Settlement Account',
        entityType: 'tier1_bank',
        purpose: 'Institutional settlement for Tier-1 banks',
        permissions: {
          canIssue: false,
          canRedeem: false,
          canSettle: true,
          canViewAll: false,
        },
      },
      {
        accountClass: 'GRA-4',
        className: 'Tier-2 Bank Liquidity Account',
        entityType: 'tier2_bank',
        purpose: 'Liquidity/FX integration for Tier-2 banks',
        permissions: {
          canIssue: false,
          canRedeem: false,
          canSettle: true,
          canViewAll: false,
        },
      },
      {
        accountClass: 'GRA-5',
        className: 'Enterprise Synthetic Access Account',
        entityType: 'enterprise',
        purpose: 'Synthetic access for enterprises',
        permissions: {
          canIssue: false,
          canRedeem: false,
          canSettle: false,
          canViewAll: false,
        },
      },
      {
        accountClass: 'GRA-6',
        className: 'Observational Analytics Account',
        entityType: 'observational',
        purpose: 'Non-settlement analytics account',
        permissions: {
          canIssue: false,
          canRedeem: false,
          canSettle: false,
          canViewAll: false,
        },
      },
    ];

    for (const accountClass of accountClasses) {
      await prisma.gruAccountClass.upsert({
        where: { accountClass: accountClass.accountClass },
        update: {},
        create: accountClass,
      });
    }
  }

  /**
   * Create a new GRU account with specified class
   */
  async createGruAccount(
    request: CreateGruAccountRequest
  ): Promise<GruAccountInfo> {
    // Ensure account classes are initialized
    await this.initializeAccountClasses();

    // Validate account class exists
    const accountClass = await prisma.gruAccountClass.findUnique({
      where: { accountClass: request.accountClass },
    });

    if (!accountClass) {
      throw new Error(`Invalid account class: ${request.accountClass}`);
    }

    // Validate entity type matches account class
    if (accountClass.entityType !== request.entityType) {
      throw new Error(
        `Entity type ${request.entityType} does not match account class ${request.accountClass} entity type ${accountClass.entityType}`
      );
    }

    // Generate account number
    const accountNumber = this.generateAccountNumber(request.accountClass);

    const initialBalance = request.initialBalance
      ? new Decimal(request.initialBalance)
      : new Decimal(0);

    // Create account
    const account = await prisma.gruAccount.create({
      data: {
        accountId: `GRU-ACC-${uuidv4()}`,
        accountClass: request.accountClass,
        entityId: request.entityId,
        entityType: request.entityType,
        accountNumber,
        balance: initialBalance,
        availableBalance: initialBalance,
        reservedBalance: new Decimal(0),
        currencyCode: 'GRU',
        status: 'active',
      },
    });

    return this.mapToAccountInfo(account);
  }

  /**
   * Get GRU account by ID
   */
  async getGruAccount(accountId: string): Promise<GruAccountInfo | null> {
    const account = await prisma.gruAccount.findUnique({
      where: { accountId },
    });

    if (!account) {
      return null;
    }

    return this.mapToAccountInfo(account);
  }

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

    if (!account) {
      return null;
    }

    return this.mapToAccountInfo(account);
  }

  /**
   * Get accounts by class
   */
  async getGruAccountsByClass(accountClass: GruAccountClass): Promise<GruAccountInfo[]> {
    const accounts = await prisma.gruAccount.findMany({
      where: { accountClass, status: 'active' },
      orderBy: { createdAt: 'desc' },
    });

    return accounts.map((account) => this.mapToAccountInfo(account));
  }

  /**
   * Get accounts by entity
   */
  async getGruAccountsByEntity(entityId: string): Promise<GruAccountInfo[]> {
    const accounts = await prisma.gruAccount.findMany({
      where: { entityId, status: 'active' },
      orderBy: { createdAt: 'desc' },
    });

    return accounts.map((account) => this.mapToAccountInfo(account));
  }

  /**
   * Update account status
   */
  async updateAccountStatus(
    accountId: string,
    status: 'active' | 'suspended' | 'closed'
  ): Promise<GruAccountInfo> {
    const updateData: any = { status };

    if (status === 'closed') {
      updateData.closedAt = new Date();
    }

    const account = await prisma.gruAccount.update({
      where: { accountId },
      data: updateData,
    });

    return this.mapToAccountInfo(account);
  }

  /**
   * Validate account class permissions
   */
  async validateAccountClass(
    accountId: string,
    permission: 'canIssue' | 'canRedeem' | 'canSettle' | 'canViewAll'
  ): Promise<boolean> {
    const account = await prisma.gruAccount.findUnique({
      where: { accountId },
      include: { accountClassDef: true },
    });

    if (!account || !account.accountClassDef) {
      return false;
    }

    const permissions = account.accountClassDef.permissions as any;
    return permissions[permission] === true;
  }

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

    if (!account) {
      throw new Error(`Account not found: ${accountId}`);
    }

    const amountDecimal = new Decimal(amount);
    const newAvailableBalance = account.availableBalance.minus(amountDecimal);
    const newReservedBalance = account.reservedBalance.plus(amountDecimal);

    if (newAvailableBalance.lt(0)) {
      throw new Error('Insufficient available balance');
    }

    await prisma.gruAccount.update({
      where: { accountId },
      data: {
        availableBalance: newAvailableBalance,
        reservedBalance: newReservedBalance,
      },
    });
  }

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

    if (!account) {
      throw new Error(`Account not found: ${accountId}`);
    }

    const amountDecimal = new Decimal(amount);
    const newReservedBalance = account.reservedBalance.minus(amountDecimal);
    const newAvailableBalance = account.availableBalance.plus(amountDecimal);

    if (newReservedBalance.lt(0)) {
      throw new Error('Insufficient reserved balance');
    }

    await prisma.gruAccount.update({
      where: { accountId },
      data: {
        availableBalance: newAvailableBalance,
        reservedBalance: newReservedBalance,
      },
    });
  }

  /**
   * Generate account number
   */
  private generateAccountNumber(accountClass: GruAccountClass): string {
    const timestamp = Date.now().toString(36).toUpperCase();
    const random = Math.random().toString(36).substring(2, 6).toUpperCase();
    return `${accountClass}-${timestamp}-${random}`;
  }

  /**
   * Map database model to account info
   */
  private mapToAccountInfo(account: any): GruAccountInfo {
    return {
      id: account.id,
      accountId: account.accountId,
      accountClass: account.accountClass,
      entityId: account.entityId,
      entityType: account.entityType,
      accountNumber: account.accountNumber,
      balance: account.balance.toString(),
      availableBalance: account.availableBalance.toString(),
      reservedBalance: account.reservedBalance.toString(),
      status: account.status,
      openedAt: account.openedAt,
    };
  }
}

export const gruAccountService = new GruAccountService();

