/**
 * GAAP / SOX Accounting Guardrails — Non-Overridable
 * 
 * Enforces referential integrity, soft-delete-only policies, immutable
 * posted transactions, and tamper-proof audit logging across ALL GFS modules.
 * 
 * These rules are BINDING and cannot be bypassed by any user, agent, or
 * The Hearth generation. See Standing Orders §13.
 */

import { prisma } from '@/lib/db';
import { Prisma } from '@prisma/client';

// ─────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────

export type GuardrailEntity = 'customer' | 'vendor' | 'product' | 'account';
export type GuardrailAction = 'delete' | 'void' | 'edit' | 'inactivate';

export interface GuardrailResult {
  allowed: boolean;
  action: 'hard_delete' | 'soft_delete' | 'void' | 'blocked';
  reason: string;
  hasHistory: boolean;
  linkedRecords: {
    salesOrders: number;
    invoices: number;
    opportunities: number;
    quotes: number;
    contacts: number;
    leads: number;
    transactions: number;
    vendorInvoices: number;
    inventoryItems: number;
    stockMovements: number;
    purchaseOrderItems: number;
  };
}

export interface VoidResult {
  allowed: boolean;
  reason: string;
  requiresApproval: boolean;
  reasonCodeRequired: boolean;
}

// ─────────────────────────────────────────────
// Constants
// ─────────────────────────────────────────────

/** Statuses that mark a transaction as posted/finalized */
const POSTED_STATUSES = ['posted', 'paid', 'completed', 'closed', 'approved', 'fulfilled', 'shipped', 'delivered'];

/** Amount threshold requiring approval for void operations */
const MATERIAL_AMOUNT_THRESHOLD = 1000;

// ─────────────────────────────────────────────
// 1. Referential Integrity Check
// ─────────────────────────────────────────────

/**
 * Check if an entity can be deleted. Returns whether hard-delete, soft-delete,
 * or complete block is appropriate based on linked records.
 */
export async function checkDeleteAllowed(
  entityType: GuardrailEntity,
  entityId: string
): Promise<GuardrailResult> {
  const linked = {
    salesOrders: 0,
    invoices: 0,
    opportunities: 0,
    quotes: 0,
    contacts: 0,
    leads: 0,
    transactions: 0,
    vendorInvoices: 0,
    inventoryItems: 0,
    stockMovements: 0,
    purchaseOrderItems: 0,
  };

  switch (entityType) {
    case 'customer': {
      const [salesOrders, invoices, opportunities, quotes, contacts, leads] = await Promise.all([
        prisma.salesOrder.count({ where: { customerId: entityId } }).catch(() => 0),
        prisma.invoice.count({ where: { customerId: entityId } }).catch(() => 0),
        prisma.opportunity.count({ where: { customerId: entityId } }).catch(() => 0),
        prisma.quote.count({ where: { customerId: entityId } }).catch(() => 0),
        prisma.contact.count({ where: { customerId: entityId } }).catch(() => 0),
        prisma.lead.count({ where: { customerId: entityId } }).catch(() => 0),
      ]);
      linked.salesOrders = salesOrders;
      linked.invoices = invoices;
      linked.opportunities = opportunities;
      linked.quotes = quotes;
      linked.contacts = contacts;
      linked.leads = leads;
      break;
    }
    case 'vendor': {
      const vendorInvoices = await prisma.vendorInvoice.count({ where: { vendorId: entityId } }).catch(() => 0);
      linked.vendorInvoices = vendorInvoices;
      break;
    }
    case 'product': {
      const [inventoryItems, stockMovements, purchaseOrderItems] = await Promise.all([
        prisma.inventoryItem.count({ where: { productId: entityId } }).catch(() => 0),
        prisma.stockMovement.count({ where: { productId: entityId } }).catch(() => 0),
        prisma.purchaseOrderItem.count({ where: { productId: entityId } }).catch(() => 0),
      ]);
      linked.inventoryItems = inventoryItems;
      linked.stockMovements = stockMovements;
      linked.purchaseOrderItems = purchaseOrderItems;
      break;
    }
    case 'account': {
      const transactions = await prisma.transaction.count({ where: { accountId: entityId } }).catch(() => 0);
      linked.transactions = transactions;
      break;
    }
  }

  const totalLinked = Object.values(linked).reduce((s, v) => s + v, 0);
  const hasHistory = totalLinked > 0;

  if (!hasHistory) {
    // No linked records — hard delete is safe
    return {
      allowed: true,
      action: 'hard_delete',
      reason: 'No linked transactions or history — safe to delete.',
      hasHistory: false,
      linkedRecords: linked,
    };
  }

  // Has history — only soft-delete (mark inactive) allowed
  return {
    allowed: true,
    action: 'soft_delete',
    reason: `Cannot delete — ${totalLinked} active transaction(s) attached. Use "Mark Inactive" instead.`,
    hasHistory: true,
    linkedRecords: linked,
  };
}

// ─────────────────────────────────────────────
// 2. Posted Transaction Immutability
// ─────────────────────────────────────────────

/**
 * Check if a posted transaction can be voided.
 * Posted transactions can NEVER be deleted — only voided with reason + audit trail.
 */
export function checkVoidAllowed(
  status: string,
  amount: number,
  _entityType: 'invoice' | 'sales_order' | 'transaction' | 'vendor_invoice'
): VoidResult {
  const isPosted = POSTED_STATUSES.includes(status.toLowerCase());
  const isMaterial = Math.abs(amount) >= MATERIAL_AMOUNT_THRESHOLD;

  if (!isPosted) {
    // Draft/pending — can be cancelled or voided freely
    return {
      allowed: true,
      reason: 'Transaction is in draft/pending status — can be cancelled.',
      requiresApproval: false,
      reasonCodeRequired: false,
    };
  }

  // Posted — void allowed but with controls
  return {
    allowed: true,
    reason: 'Posted transaction — void will zero the amount and create a reversing entry. Original record preserved.',
    requiresApproval: isMaterial,
    reasonCodeRequired: true,
  };
}

/**
 * Check if a transaction can be edited.
 * Posted transactions are immutable — no edits allowed.
 */
export function checkEditAllowed(status: string): { allowed: boolean; reason: string } {
  const isPosted = POSTED_STATUSES.includes(status.toLowerCase());
  
  if (isPosted) {
    return {
      allowed: false,
      reason: 'Posted/finalized transaction cannot be edited. Use Void + new entry, or Credit Memo.',
    };
  }

  return {
    allowed: true,
    reason: 'Transaction is in draft/pending status — edits allowed.',
  };
}

// ─────────────────────────────────────────────
// 3. Soft Delete (Mark Inactive)
// ─────────────────────────────────────────────

/**
 * Mark an entity as inactive (soft delete).
 * Hides from active lists but preserves all data for audits/reports/tax.
 */
export async function markInactive(
  entityType: GuardrailEntity,
  entityId: string,
  performedBy: string,
  reason: string
): Promise<{ success: boolean; error?: string }> {
  try {
    let previousState: Record<string, unknown> = {};
    let newState: Record<string, unknown> = {};

    switch (entityType) {
      case 'customer': {
        const before = await prisma.customer.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Customer not found' };
        previousState = { status: before.status };
        await prisma.customer.update({
          where: { id: entityId },
          data: { status: 'inactive' },
        });
        newState = { status: 'inactive' };
        break;
      }
      case 'vendor': {
        const before = await prisma.vendor.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Vendor not found' };
        previousState = { isActive: before.isActive };
        await prisma.vendor.update({
          where: { id: entityId },
          data: { isActive: false },
        });
        newState = { isActive: false };
        break;
      }
      case 'product': {
        const before = await prisma.product.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Product not found' };
        previousState = { isActive: before.isActive };
        await prisma.product.update({
          where: { id: entityId },
          data: { isActive: false },
        });
        newState = { isActive: false };
        break;
      }
      case 'account': {
        const before = await prisma.account.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Account not found' };
        previousState = { isActive: before.isActive };
        await prisma.account.update({
          where: { id: entityId },
          data: { isActive: false },
        });
        newState = { isActive: false };
        break;
      }
    }

    // Log to immutable audit trail
    await logAuditEvent({
      action: 'inactivate',
      resource: entityType,
      resourceId: entityId,
      performedBy,
      reason,
      previousState,
      newState,
    });

    return { success: true };
  } catch (error) {
    console.error(`[GUARDRAIL] Failed to mark ${entityType} inactive:`, error);
    return { success: false, error: String(error) };
  }
}

// ─────────────────────────────────────────────
// 4. Void Transaction
// ─────────────────────────────────────────────

/**
 * Void a posted transaction. Zeros the amount, preserves the record,
 * and creates a reversing audit entry.
 */
export async function voidTransaction(
  entityType: 'invoice' | 'sales_order' | 'transaction' | 'vendor_invoice',
  entityId: string,
  performedBy: string,
  reasonCode: string,
  reasonNote: string
): Promise<{ success: boolean; error?: string }> {
  try {
    let previousState: Record<string, unknown> = {};

    switch (entityType) {
      case 'invoice': {
        const before = await prisma.invoice.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Invoice not found' };
        previousState = { status: before.status, total: before.total, amountPaid: before.amountPaid };
        await prisma.invoice.update({
          where: { id: entityId },
          data: {
            status: 'voided',
            metadata: {
              ...(typeof before.metadata === 'object' && before.metadata !== null ? before.metadata : {}),
              voidedAt: new Date().toISOString(),
              voidedBy: performedBy,
              voidReasonCode: reasonCode,
              voidReasonNote: reasonNote,
              originalTotal: before.total,
              originalAmountPaid: before.amountPaid,
            },
          },
        });
        break;
      }
      case 'sales_order': {
        const before = await prisma.salesOrder.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Sales Order not found' };
        previousState = { status: before.status, total: before.total };
        await prisma.salesOrder.update({
          where: { id: entityId },
          data: {
            status: 'voided',
            metadata: {
              ...(typeof before.metadata === 'object' && before.metadata !== null ? before.metadata : {}),
              voidedAt: new Date().toISOString(),
              voidedBy: performedBy,
              voidReasonCode: reasonCode,
              voidReasonNote: reasonNote,
              originalTotal: before.total,
            },
          },
        });
        break;
      }
      case 'transaction': {
        const before = await prisma.transaction.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Transaction not found' };
        previousState = { status: before.status, amount: before.amount };
        await prisma.transaction.update({
          where: { id: entityId },
          data: {
            status: 'voided',
            metadata: {
              ...(typeof before.metadata === 'object' && before.metadata !== null ? before.metadata : {}),
              voidedAt: new Date().toISOString(),
              voidedBy: performedBy,
              voidReasonCode: reasonCode,
              voidReasonNote: reasonNote,
              originalAmount: before.amount,
            },
          },
        });
        break;
      }
      case 'vendor_invoice': {
        const before = await prisma.vendorInvoice.findUnique({ where: { id: entityId } });
        if (!before) return { success: false, error: 'Vendor Invoice not found' };
        previousState = { status: before.status, amount: before.amount };
        await prisma.vendorInvoice.update({
          where: { id: entityId },
          data: {
            status: 'voided',
            metadata: {
              ...(typeof before.metadata === 'object' && before.metadata !== null ? before.metadata : {}),
              voidedAt: new Date().toISOString(),
              voidedBy: performedBy,
              voidReasonCode: reasonCode,
              voidReasonNote: reasonNote,
              originalAmount: before.amount,
            },
          },
        });
        break;
      }
    }

    // Log to immutable audit trail
    await logAuditEvent({
      action: 'void',
      resource: entityType,
      resourceId: entityId,
      performedBy,
      reason: `${reasonCode}: ${reasonNote}`,
      previousState,
      newState: { status: 'voided' },
    });

    return { success: true };
  } catch (error) {
    console.error(`[GUARDRAIL] Failed to void ${entityType}:`, error);
    return { success: false, error: String(error) };
  }
}

// ─────────────────────────────────────────────
// 5. Immutable Audit Trail
// ─────────────────────────────────────────────

interface AuditEventInput {
  action: string;
  resource: string;
  resourceId: string;
  performedBy: string;
  reason: string;
  previousState: Record<string, unknown>;
  newState: Record<string, unknown>;
}

/**
 * Log an immutable audit event. Every create, edit, void, inactivation,
 * or attempted delete MUST be logged here.
 */
export async function logAuditEvent(event: AuditEventInput): Promise<void> {
  try {
    await prisma.auditLog.create({
      data: {
        action: event.action,
        resource: event.resource,
        resourceId: event.resourceId,
        details: {
          performedBy: event.performedBy,
          reason: event.reason,
          timestamp: new Date().toISOString(),
        },
        previousState: event.previousState as Prisma.InputJsonValue,
        newState: event.newState as Prisma.InputJsonValue,
        success: true,
      },
    });
  } catch (error) {
    // Audit logging failure must NOT prevent the operation but MUST be flagged
    console.error('[GUARDRAIL][CRITICAL] Audit log write failed:', error);
  }
}

/**
 * Log an attempted (blocked) delete for compliance records.
 */
export async function logBlockedDelete(
  resource: string,
  resourceId: string,
  attemptedBy: string,
  reason: string
): Promise<void> {
  await logAuditEvent({
    action: 'delete_blocked',
    resource,
    resourceId,
    performedBy: attemptedBy,
    reason,
    previousState: {},
    newState: {},
  });
}

// ─────────────────────────────────────────────
// 6. Guardrail Summary (for agents + UI)
// ─────────────────────────────────────────────

export const GUARDRAIL_RULES = {
  referentialIntegrity: [
    'Block hard delete of Customer, Vendor, Product, or Account if linked Sales Orders, Invoices, Payments, Credits, Balances, or history exist.',
    'Return clear error: "Cannot delete – active transactions attached."',
  ],
  softDeleteOnly: [
    'Customers/Vendors with history = Soft Delete Only (Mark Inactive).',
    'Inactive entities are hidden from active lists/dropdowns but kept for reports, audits, tax.',
    'UI shows "Mark Inactive" instead of Delete when history exists.',
  ],
  postedImmutability: [
    'Posted invoices, sales orders, and credits can NEVER be deleted.',
    'Only Void (zeros amount, keeps record + audit trail) or Credit Memo + Reversing Entry allowed.',
    'Mandatory Reason Code required for all voids.',
    'Material amounts (>=$' + MATERIAL_AMOUNT_THRESHOLD + ') require approval workflow.',
  ],
  auditTrail: [
    'Every create, edit, void, inactivation, or attempted delete is logged.',
    'Audit records include: who, when, what changed, reason, before/after values.',
    'Audit logs are immutable — no edits or deletes of audit records permitted.',
  ],
  additionalControls: [
    'Closed period locking: No changes to closed periods without reversal + approval.',
    'Segregation of duties: Different users for entry vs. approval (configurable roles).',
    'Reconciliation: Block period close if sub-ledgers do not balance to GL.',
    'No orphaned records: All foreign-key relationships enforced.',
  ],
} as const;

/** Reason codes for void operations */
export const VOID_REASON_CODES = [
  { code: 'DUPLICATE', label: 'Duplicate Entry' },
  { code: 'ERROR', label: 'Data Entry Error' },
  { code: 'CUSTOMER_REQUEST', label: 'Customer Request' },
  { code: 'FRAUD', label: 'Fraud / Suspicious Activity' },
  { code: 'DISPUTE', label: 'Dispute Resolution' },
  { code: 'RETURN', label: 'Return / Refund' },
  { code: 'WRITE_OFF', label: 'Write-Off' },
  { code: 'OTHER', label: 'Other (specify in notes)' },
] as const;
