/**
 * HR Banking & Payroll System
 * Manages employee bank accounts and payroll processing
 * 
 * SECURITY: All sensitive financial data (account numbers, routing numbers)
 * is encrypted at rest using AES-256-GCM via lib/gfs/crypto.ts
 */

import { prisma } from '@/lib/db';
import { encrypt, decrypt } from '@/lib/gfs/crypto';
import { auditLogger } from './audit-logger';

export interface BankAccountInput {
  accountName: string;
  bankName: string;
  accountNumber: string;
  routingNumber: string;
  accountType?: 'checking' | 'savings';
  isPrimary?: boolean;
}

/**
 * Helper to check if a value is encrypted (contains our format markers)
 */
function isEncrypted(value: string): boolean {
  // Our encryption format is: salt:iv:authTag:encrypted (4 parts separated by colons)
  return value.split(':').length === 4;
}

/**
 * Safely decrypt a value, handling both encrypted and plaintext data
 * (for backwards compatibility during migration)
 */
function safeDecrypt(value: string): string {
  if (!value) return value;
  if (isEncrypted(value)) {
    try {
      return decrypt(value);
    } catch (e) {
      console.error('[HR Banking] Decryption failed, returning masked value');
      return '****ERROR';
    }
  }
  // Not encrypted (legacy data) - return as-is but log warning
  console.warn('[HR Banking] Unencrypted sensitive data detected - consider running migration');
  return value;
}

/**
 * Get last 4 digits of a potentially encrypted account number
 */
function getLast4(encryptedOrPlain: string): string {
  const decrypted = safeDecrypt(encryptedOrPlain);
  return decrypted.slice(-4);
}

/**
 * Add bank account to employee
 * Account numbers and routing numbers are encrypted before storage
 */
export async function addBankAccount(
  employeeId: string,
  input: BankAccountInput,
  addedById?: string
): Promise<string> {
  // If this is set as primary, unset other primary accounts
  if (input.isPrimary) {
    await prisma.hRBankAccount.updateMany({
      where: { employeeId, isPrimary: true },
      data: { isPrimary: false },
    });
  }

  // Encrypt sensitive financial data before storage
  const encryptedAccountNumber = encrypt(input.accountNumber);
  const encryptedRoutingNumber = encrypt(input.routingNumber);

  const account = await prisma.hRBankAccount.create({
    data: {
      employeeId,
      accountName: input.accountName,
      bankName: input.bankName,
      accountNumber: encryptedAccountNumber,
      routingNumber: encryptedRoutingNumber,
      accountType: input.accountType || 'checking',
      isPrimary: input.isPrimary ?? false,
    },
  });

  await auditLogger
    .setContext({ userId: addedById })
    .logCrud('create', 'bank_account', account.id, {
      newState: {
        employeeId,
        bankName: input.bankName,
        accountType: input.accountType,
        isPrimary: input.isPrimary,
        // Never log full account numbers - only last 4
        accountNumberLast4: input.accountNumber.slice(-4),
      },
    });

  return account.id;
}

/**
 * Get employee bank accounts (with masked sensitive data for display)
 */
export async function getEmployeeBankAccounts(employeeId: string) {
  const accounts = await prisma.hRBankAccount.findMany({
    where: { employeeId, isActive: true },
    orderBy: [{ isPrimary: 'desc' }, { createdAt: 'asc' }],
  });

  // Return masked sensitive data for display purposes
  return accounts.map((a: any) => ({
    ...a,
    accountNumber: `****${getLast4(a.accountNumber)}`,
    routingNumber: `****${getLast4(a.routingNumber)}`,
  }));
}

/**
 * Get decrypted bank account details (for processing payments)
 * This should only be called by authorized payment processing functions
 */
export async function getDecryptedBankAccount(accountId: string) {
  const account = await prisma.hRBankAccount.findUnique({ where: { id: accountId } });
  if (!account) return null;
  
  return {
    ...account,
    accountNumber: safeDecrypt(account.accountNumber),
    routingNumber: safeDecrypt(account.routingNumber),
  };
}

/**
 * Update bank account
 * Encrypts sensitive data if provided
 */
export async function updateBankAccount(
  accountId: string,
  updates: Partial<BankAccountInput>,
  updatedById?: string
): Promise<void> {
  const account = await prisma.hRBankAccount.findUnique({ where: { id: accountId } });
  if (!account) throw new Error('Bank account not found');

  // If setting as primary, unset other primary accounts
  if (updates.isPrimary) {
    await prisma.hRBankAccount.updateMany({
      where: { employeeId: account.employeeId, isPrimary: true, id: { not: accountId } },
      data: { isPrimary: false },
    });
  }

  // Encrypt sensitive fields if they are being updated
  const encryptedUpdates: Record<string, any> = { ...updates };
  if (updates.accountNumber) {
    encryptedUpdates.accountNumber = encrypt(updates.accountNumber);
  }
  if (updates.routingNumber) {
    encryptedUpdates.routingNumber = encrypt(updates.routingNumber);
  }

  await prisma.hRBankAccount.update({
    where: { id: accountId },
    data: encryptedUpdates,
  });

  await auditLogger
    .setContext({ userId: updatedById })
    .logCrud('update', 'bank_account', accountId, {
      description: 'Bank account updated',
      // Log last 4 if account number was changed
      ...(updates.accountNumber && { accountNumberLast4: updates.accountNumber.slice(-4) }),
    });
}

/**
 * Deactivate bank account
 */
export async function deactivateBankAccount(
  accountId: string,
  deactivatedById?: string
): Promise<void> {
  await prisma.hRBankAccount.update({
    where: { id: accountId },
    data: { isActive: false },
  });

  await auditLogger
    .setContext({ userId: deactivatedById })
    .logCrud('delete', 'bank_account', accountId, {
      description: 'Bank account deactivated',
    });
}

/**
 * Create a new payroll run
 */
export async function createPayrollRun(
  periodStart: Date,
  periodEnd: Date,
  createdById?: string
): Promise<string> {
  const runNumber = `PR-${periodStart.getFullYear()}${String(periodStart.getMonth() + 1).padStart(2, '0')}-${Date.now().toString(36).toUpperCase()}`;
  const period = `${periodStart.toISOString().split('T')[0]} to ${periodEnd.toISOString().split('T')[0]}`;

  // Get active employees
  const employees = await prisma.employee.findMany({
    where: { status: 'active' },
    include: {
      bankAccounts: {
        where: { isActive: true, isPrimary: true },
      },
    },
  });

  // Calculate totals (simplified - real implementation would be more complex)
  let totalGross = 0;
  let totalDeductions = 0;

  for (const emp of employees) {
    if (emp.salary) {
      const gross = emp.payFrequency === 'monthly' ? emp.salary : emp.salary / 2; // bi-weekly assumed
      totalGross += gross;
      // Simple tax calculation (real would be much more complex)
      totalDeductions += gross * 0.25; // 25% total deductions
    }
  }

  const totalNet = totalGross - totalDeductions;

  const run = await prisma.payrollRun.create({
    data: {
      runNumber,
      period,
      periodStart,
      periodEnd,
      status: 'draft',
      totalGross,
      totalDeductions,
      totalNet,
      employeeCount: employees.length,
    },
  });

  // Create individual payments
  for (const emp of employees) {
    if (emp.salary) {
      const gross = emp.payFrequency === 'monthly' ? emp.salary : emp.salary / 2;
      const deductions = {
        federalTax: gross * 0.12,
        stateTax: gross * 0.05,
        socialSecurity: gross * 0.062,
        medicare: gross * 0.0145,
        insurance: 200,
      };
      const totalDed = Object.values(deductions).reduce((a, b) => a + b, 0);
      const net = gross - totalDed;

      await prisma.payrollPayment.create({
        data: {
          runId: run.id,
          employeeId: emp.id,
          bankAccountId: emp.bankAccounts[0]?.id,
          grossAmount: gross,
          deductions,
          netAmount: net,
          status: 'pending',
        },
      });
    }
  }

  await auditLogger
    .setContext({ userId: createdById })
    .logCrud('create', 'payroll', run.id, {
      newState: {
        runNumber,
        period,
        employeeCount: employees.length,
        totalGross,
        totalNet,
      },
    });

  return run.id;
}

/**
 * Get payroll run details
 */
export async function getPayrollRun(runId: string) {
  return prisma.payrollRun.findUnique({
    where: { id: runId },
    include: {
      payments: {
        include: {
          // We can't include employee directly since there's no relation
          // In a real app, you'd join with employee table
        },
      },
    },
  });
}

/**
 * Get payroll runs list
 */
export async function getPayrollRuns(options?: {
  status?: string;
  limit?: number;
  offset?: number;
}) {
  const where = options?.status ? { status: options.status } : {};

  const [runs, total] = await Promise.all([
    prisma.payrollRun.findMany({
      where,
      orderBy: { periodStart: 'desc' },
      take: options?.limit || 20,
      skip: options?.offset || 0,
    }),
    prisma.payrollRun.count({ where }),
  ]);

  return { runs, total };
}

/**
 * Submit payroll for approval
 */
export async function submitPayrollForApproval(
  runId: string,
  submittedById?: string
): Promise<void> {
  const run = await prisma.payrollRun.findUnique({ where: { id: runId } });
  if (!run || run.status !== 'draft') {
    throw new Error('Payroll run cannot be submitted');
  }

  await prisma.payrollRun.update({
    where: { id: runId },
    data: { status: 'pending_approval' },
  });

  await auditLogger
    .setContext({ userId: submittedById })
    .logCrud('update', 'payroll', runId, {
      previousState: { status: 'draft' },
      newState: { status: 'pending_approval' },
      description: 'Payroll submitted for approval',
    });
}

/**
 * Approve payroll run
 */
export async function approvePayroll(
  runId: string,
  approvedById: string
): Promise<void> {
  const run = await prisma.payrollRun.findUnique({ where: { id: runId } });
  if (!run || run.status !== 'pending_approval') {
    throw new Error('Payroll run cannot be approved');
  }

  await prisma.payrollRun.update({
    where: { id: runId },
    data: {
      status: 'approved',
      approvedBy: approvedById,
      approvedAt: new Date(),
    },
  });

  await auditLogger
    .setContext({ userId: approvedById })
    .logCrud('update', 'payroll', runId, {
      previousState: { status: 'pending_approval' },
      newState: { status: 'approved' },
      description: 'Payroll approved',
    });
}

/**
 * Process payroll (execute payments)
 */
export async function processPayroll(
  runId: string,
  processedById?: string
): Promise<{ success: number; failed: number }> {
  const run = await prisma.payrollRun.findUnique({
    where: { id: runId },
    include: { payments: true },
  });

  if (!run || run.status !== 'approved') {
    throw new Error('Payroll run cannot be processed');
  }

  await prisma.payrollRun.update({
    where: { id: runId },
    data: { status: 'processing', processedAt: new Date() },
  });

  let success = 0;
  let failed = 0;

  for (const payment of run.payments) {
    try {
      // In real implementation, this would initiate ACH transfer
      // For now, we just mark as completed
      await prisma.payrollPayment.update({
        where: { id: payment.id },
        data: {
          status: 'completed',
          processedAt: new Date(),
          transactionRef: `TXN-${Date.now().toString(36).toUpperCase()}`,
        },
      });
      success++;
    } catch (error: any) {
      await prisma.payrollPayment.update({
        where: { id: payment.id },
        data: {
          status: 'failed',
          error: error.message,
        },
      });
      failed++;
    }
  }

  const finalStatus = failed === 0 ? 'completed' : (success === 0 ? 'failed' : 'completed');

  await prisma.payrollRun.update({
    where: { id: runId },
    data: {
      status: finalStatus,
      completedAt: new Date(),
      error: failed > 0 ? `${failed} payments failed` : null,
    },
  });

  await auditLogger
    .setContext({ userId: processedById })
    .logCrud('update', 'payroll', runId, {
      previousState: { status: 'approved' },
      newState: { status: finalStatus },
      description: `Payroll processed: ${success} success, ${failed} failed`,
    });

  return { success, failed };
}
