/**
 * CRM-to-OrderToCash Bridge
 * Connects CRM SalesOrder lifecycle to the autonomous order-to-cash pipeline
 * Ensures: CRM Order → Validation → Invoice → Transaction → AR → Fulfillment → Reconciliation → Complete
 * Full audit trail at every step
 */

import prisma from '@/lib/db';
import { AgentContext, StageResult } from './types';
import { logAgentAction } from './agent-logger';
import { initializeOrderExecution, advanceStage } from './orchestrator';

// CRM-specific stage flow (slightly different from swag store)
const CRM_STAGES = [
  'order_placed',      // SalesOrder created in CRM
  'order_validated',   // Auto-validate line items, customer, pricing
  'invoice_generated', // Auto-generate invoice from order
  'transaction_recorded', // Record financial transaction
  'ar_updated',        // Update accounts receivable
  'fulfillment_started', // Mark fulfillment started
  'fulfilled',         // Order fulfilled
  'cash_reconciled',   // Payment reconciled
  'completed',         // Done
] as const;

export interface CrmOrderInput {
  customerId: string;
  items: Array<{
    description: string;
    quantity: number;
    unitPrice: number;
    productId?: string;
  }>;
  notes?: string;
  shippingAddress?: Record<string, unknown>;
  paymentMethod?: string;
  requiredDate?: string;
  createdBy?: string;
}

/**
 * Process a new CRM SalesOrder through the full order-to-cash pipeline.
 * Returns the created order + execution tracking IDs.
 */
export async function processCrmOrder(input: CrmOrderInput) {
  const startTime = Date.now();

  // 1. Validate customer exists
  const customer = await prisma.customer.findUnique({
    where: { id: input.customerId },
  });
  if (!customer) {
    throw new Error(`Customer ${input.customerId} not found`);
  }

  // 2. Generate order number
  const count = await prisma.salesOrder.count();
  const orderNumber = `SO-${String(count + 1).padStart(6, '0')}`;

  // 3. Calculate totals
  let subtotal = 0;
  const orderItems = input.items.map((item, idx) => {
    const lineTotal = item.quantity * item.unitPrice;
    subtotal += lineTotal;
    return {
      description: item.description || 'Item',
      quantity: item.quantity,
      unitPrice: item.unitPrice,
      total: lineTotal,
      productId: item.productId || null,
      sortOrder: idx,
    };
  });

  const taxRate = 0.08;
  const taxAmount = Math.round(subtotal * taxRate * 100) / 100;
  const totalAmount = Math.round((subtotal + taxAmount) * 100) / 100;

  // 4. Create SalesOrder with items in a single transaction
  const order = await prisma.salesOrder.create({
    data: {
      orderNumber,
      customerId: input.customerId,
      status: 'confirmed',
      subtotal,
      tax: taxAmount,
      total: totalAmount,
      notes: input.notes,
      shippingAddress: input.shippingAddress
        ? JSON.parse(JSON.stringify(input.shippingAddress))
        : customer.shippingAddress,
      billingAddress: customer.billingAddress as any,
      paymentMethod: input.paymentMethod || customer.paymentTerms || 'Net 30',
      paymentStatus: 'pending',
      requiredDate: input.requiredDate ? new Date(input.requiredDate) : undefined,
      createdBy: input.createdBy || 'crm-system',
      items: {
        create: orderItems,
      },
    },
    include: {
      customer: { select: { id: true, name: true, customerNumber: true, email: true } },
      items: true,
    },
  });

  // 5. Initialize order-to-cash execution tracking
  const executionId = await initializeOrderExecution(order.id, orderNumber);

  const context: AgentContext = {
    executionId,
    orderId: order.id,
    orderNumber,
    agentName: 'CRM-OrderBridge',
    timestamp: new Date(),
  };

  // 6. Log the order creation
  await logAgentAction({
    context,
    actionType: 'create_transaction',
    targetEntity: 'sales_order',
    targetId: order.id,
    inputData: {
      customerId: input.customerId,
      customerName: customer.name,
      itemCount: input.items.length,
    },
    outputData: {
      orderNumber,
      subtotal,
      tax: taxAmount,
      total: totalAmount,
      status: 'confirmed',
    },
    executionTimeMs: Date.now() - startTime,
  });

  // 7. Also write to the main AuditLog for Sentinel visibility
  await prisma.auditLog.create({
    data: {
      action: 'create',
      resource: 'sales_order',
      resourceId: order.id,
      details: {
        orderNumber,
        customerId: customer.id,
        customerName: customer.name,
        total: totalAmount,
        itemCount: orderItems.length,
        source: 'crm',
        executionId,
      },
      success: true,
    },
  });

  // 8. Run validation step
  await validateCrmOrder(order.id, executionId, context);

  // 9. Auto-generate invoice
  const invoiceId = await generateCrmInvoice(order.id, executionId, context);

  // 10. Record financial transaction
  const transactionId = await createCrmTransaction(order.id, executionId, context);

  // 11. Update accounts receivable
  const arId = await updateCrmAccountsReceivable(order.id, invoiceId, executionId, context);

  // 12. Mark execution at fulfillment stage
  await advanceStage(executionId, {
    success: true,
    stage: 'ar_updated',
    nextStage: 'fulfilled',
    data: {
      invoiceId,
      transactionId,
      arId,
      awaitingFulfillment: true,
    },
  });

  // Log completion of automated steps
  await prisma.auditLog.create({
    data: {
      action: 'system_event',
      resource: 'order',
      resourceId: order.id,
      details: {
        event: 'crm_order_pipeline_auto_steps_complete',
        orderNumber,
        invoiceId,
        transactionId,
        arId,
        executionId,
        totalTimeMs: Date.now() - startTime,
      },
      success: true,
    },
  });

  return {
    order: {
      ...order,
      totalAmount: order.total,
      taxAmount: order.tax,
    },
    executionId,
    invoiceId,
    transactionId,
    arId,
    pipeline: {
      currentStage: 'fulfilled',
      automated: true,
      humanInterventions: 0,
    },
  };
}

/**
 * Validate the CRM order — check customer credit, item availability, pricing sanity
 */
async function validateCrmOrder(
  orderId: string,
  executionId: string,
  context: AgentContext
): Promise<void> {
  const startTime = Date.now();
  const order = await prisma.salesOrder.findUnique({
    where: { id: orderId },
    include: { customer: true, items: true },
  });

  if (!order) throw new Error(`SalesOrder ${orderId} not found`);

  const warnings: string[] = [];

  // Check customer credit limit
  if (order.customer.creditLimit && order.total > order.customer.creditLimit) {
    warnings.push(
      `Order total $${order.total} exceeds customer credit limit $${order.customer.creditLimit}`
    );
  }

  // Check for zero-price items
  for (const item of order.items) {
    if (item.unitPrice <= 0) {
      warnings.push(`Item "${item.description}" has invalid price: $${item.unitPrice}`);
    }
    if (item.quantity <= 0) {
      warnings.push(`Item "${item.description}" has invalid quantity: ${item.quantity}`);
    }
  }

  // Check math consistency
  const calculatedSubtotal = order.items.reduce((sum, i) => sum + i.total, 0);
  if (Math.abs(calculatedSubtotal - order.subtotal) > 0.01) {
    warnings.push(
      `Subtotal mismatch: items sum to $${calculatedSubtotal}, order shows $${order.subtotal}`
    );
  }

  await logAgentAction({
    context: { ...context, agentName: 'OrderValidator' },
    actionType: 'detect_anomaly',
    targetEntity: 'sales_order',
    targetId: orderId,
    inputData: { total: order.total, itemCount: order.items.length },
    outputData: {
      valid: warnings.length === 0,
      warnings,
    },
    confidence: warnings.length === 0 ? 1.0 : 0.7,
    executionTimeMs: Date.now() - startTime,
  });

  await advanceStage(executionId, {
    success: true,
    stage: 'order_placed',
    nextStage: 'invoice_generated',
    data: { validated: true, warnings },
  });
}

/**
 * Generate an invoice from the CRM SalesOrder
 */
async function generateCrmInvoice(
  orderId: string,
  executionId: string,
  context: AgentContext
): Promise<string> {
  const startTime = Date.now();
  const order = await prisma.salesOrder.findUnique({
    where: { id: orderId },
    include: { customer: true, items: true },
  });

  if (!order) throw new Error(`SalesOrder ${orderId} not found`);

  const invoiceNumber = `INV-${order.orderNumber}`;
  const dueDate = new Date();
  // Use customer payment terms to set due date
  const netDays = parseInt(order.customer.paymentTerms?.replace(/\D/g, '') || '30') || 30;
  dueDate.setDate(dueDate.getDate() + netDays);

  const invoiceItems = order.items.map((item) => ({
    description: item.description,
    quantity: item.quantity,
    unitPrice: item.unitPrice,
    total: item.total,
    productId: item.productId,
  }));

  const invoice = await prisma.invoice.create({
    data: {
      invoiceNumber,
      type: 'customer',
      customerId: order.customerId,
      issueDate: new Date(),
      dueDate,
      status: 'sent',
      subtotal: order.subtotal,
      tax: order.tax,
      total: order.total,
      amountPaid: 0,
      items: invoiceItems,
      metadata: {
        orderId: order.id,
        orderNumber: order.orderNumber,
        customerName: order.customer.name,
        paymentTerms: order.customer.paymentTerms,
        source: 'crm-pipeline',
      },
    },
  });

  await logAgentAction({
    context: { ...context, agentName: 'InvoiceAgent' },
    actionType: 'generate_invoice',
    targetEntity: 'invoice',
    targetId: invoice.id,
    inputData: { orderId, orderNumber: order.orderNumber },
    outputData: {
      invoiceId: invoice.id,
      invoiceNumber,
      total: order.total,
      dueDate: dueDate.toISOString(),
      paymentTerms: `Net ${netDays}`,
    },
    executionTimeMs: Date.now() - startTime,
  });

  await prisma.auditLog.create({
    data: {
      action: 'invoice_generate',
      resource: 'invoice',
      resourceId: invoice.id,
      details: {
        invoiceNumber,
        orderId,
        orderNumber: order.orderNumber,
        total: order.total,
        dueDate: dueDate.toISOString(),
        source: 'crm-pipeline',
      },
      success: true,
    },
  });

  await advanceStage(executionId, {
    success: true,
    stage: 'invoice_generated',
    nextStage: 'transaction_recorded',
    data: { invoiceId: invoice.id, invoiceNumber },
  });

  return invoice.id;
}

/**
 * Record the financial transaction against the general ledger
 */
async function createCrmTransaction(
  orderId: string,
  executionId: string,
  context: AgentContext
): Promise<string> {
  const startTime = Date.now();
  const order = await prisma.salesOrder.findUnique({
    where: { id: orderId },
    include: { customer: true },
  });

  if (!order) throw new Error(`SalesOrder ${orderId} not found`);

  // Find or create CRM revenue account
  let revenueAccount = await prisma.account.findFirst({
    where: { code: 'REV-CRM-001', type: 'revenue' },
  });

  if (!revenueAccount) {
    revenueAccount = await prisma.account.create({
      data: {
        code: 'REV-CRM-001',
        name: 'CRM Sales Revenue',
        type: 'revenue',
        category: 'income',
        balance: 0,
      },
    });
  }

  const transaction = await prisma.transaction.create({
    data: {
      type: 'income',
      accountId: revenueAccount.id,
      referenceNo: order.orderNumber,
      description: `CRM Order ${order.orderNumber} — ${order.customer.name}`,
      amount: order.total,
      date: new Date(),
      status: 'pending', // Not yet paid, will be reconciled later
      category: 'crm_sale',
      metadata: {
        orderId: order.id,
        customerId: order.customerId,
        customerName: order.customer.name,
        source: 'crm-pipeline',
      },
    },
  });

  await logAgentAction({
    context: { ...context, agentName: 'LedgerAgent' },
    actionType: 'create_transaction',
    targetEntity: 'transaction',
    targetId: transaction.id,
    inputData: { orderId, amount: order.total, accountCode: 'REV-CRM-001' },
    outputData: {
      transactionId: transaction.id,
      accountId: revenueAccount.id,
      status: 'pending',
    },
    executionTimeMs: Date.now() - startTime,
  });

  await prisma.auditLog.create({
    data: {
      action: 'payment_process',
      resource: 'transaction',
      resourceId: transaction.id,
      details: {
        orderId,
        orderNumber: order.orderNumber,
        amount: order.total,
        accountCode: 'REV-CRM-001',
        status: 'pending',
        source: 'crm-pipeline',
      },
      success: true,
    },
  });

  await advanceStage(executionId, {
    success: true,
    stage: 'transaction_recorded',
    nextStage: 'ar_updated',
    data: { transactionId: transaction.id },
  });

  return transaction.id;
}

/**
 * Create an accounts receivable entry tracking payment owed
 */
async function updateCrmAccountsReceivable(
  orderId: string,
  invoiceId: string,
  executionId: string,
  context: AgentContext
): Promise<string> {
  const startTime = Date.now();
  const order = await prisma.salesOrder.findUnique({
    where: { id: orderId },
    include: { customer: true },
  });

  if (!order) throw new Error(`SalesOrder ${orderId} not found`);

  const invoice = await prisma.invoice.findUnique({ where: { id: invoiceId } });
  if (!invoice) throw new Error(`Invoice ${invoiceId} not found`);

  const ar = await prisma.accountsReceivable.create({
    data: {
      orderId: order.id,
      invoiceId: invoice.id,
      customerId: order.customerId,
      customerEmail: order.customer.email || `${order.customer.customerNumber}@crm.local`,
      customerName: order.customer.name,
      invoiceNumber: invoice.invoiceNumber,
      originalAmount: order.total,
      amountPaid: 0,
      amountDue: order.total,
      status: 'open',
      dueDate: invoice.dueDate,
    },
  });

  await logAgentAction({
    context: { ...context, agentName: 'ARAgent' },
    actionType: 'update_ar',
    targetEntity: 'ar',
    targetId: ar.id,
    inputData: { orderId, invoiceId, amount: order.total },
    outputData: {
      arId: ar.id,
      status: 'open',
      amountDue: order.total,
      dueDate: invoice.dueDate.toISOString(),
    },
    executionTimeMs: Date.now() - startTime,
  });

  await prisma.auditLog.create({
    data: {
      action: 'create',
      resource: 'ar',
      resourceId: ar.id,
      details: {
        orderId,
        invoiceId,
        invoiceNumber: invoice.invoiceNumber,
        customerName: order.customer.name,
        amountDue: order.total,
        dueDate: invoice.dueDate.toISOString(),
        source: 'crm-pipeline',
      },
      success: true,
    },
  });

  return ar.id;
}

/**
 * Mark a CRM order as paid — updates AR, transaction, invoice status
 */
export async function markCrmOrderPaid(
  orderId: string,
  paymentDetails: {
    amountPaid: number;
    paymentMethod: string;
    referenceNo?: string;
  }
) {
  const startTime = Date.now();
  const order = await prisma.salesOrder.findUnique({
    where: { id: orderId },
    include: { customer: true },
  });

  if (!order) throw new Error(`SalesOrder ${orderId} not found`);

  // Update SalesOrder payment status
  await prisma.salesOrder.update({
    where: { id: orderId },
    data: {
      paymentStatus: 'paid',
      paymentMethod: paymentDetails.paymentMethod,
      status: 'completed',
    },
  });

  // Find and update the AR record
  const ar = await prisma.accountsReceivable.findUnique({
    where: { orderId },
  });

  if (ar) {
    await prisma.accountsReceivable.update({
      where: { id: ar.id },
      data: {
        amountPaid: paymentDetails.amountPaid,
        amountDue: Math.max(0, ar.originalAmount - paymentDetails.amountPaid),
        status: paymentDetails.amountPaid >= ar.originalAmount ? 'paid' : 'partial',
        paidDate: new Date(),
      },
    });
  }

  // Update invoice status
  const invoice = ar
    ? await prisma.invoice.findUnique({ where: { id: ar.invoiceId } })
    : null;
  if (invoice) {
    await prisma.invoice.update({
      where: { id: invoice.id },
      data: {
        status: 'paid',
        amountPaid: paymentDetails.amountPaid,
      },
    });
  }

  // Update the ledger transaction
  const txn = await prisma.transaction.findFirst({
    where: { referenceNo: order.orderNumber },
  });
  if (txn) {
    await prisma.transaction.update({
      where: { id: txn.id },
      data: { status: 'completed' },
    });

    // Update account balance
    await prisma.account.update({
      where: { id: txn.accountId },
      data: { balance: { increment: paymentDetails.amountPaid } },
    });
  }

  // Complete the execution
  const execution = await prisma.orderToCashExecution.findUnique({
    where: { orderId },
  });
  if (execution) {
    await advanceStage(execution.id, {
      success: true,
      stage: 'cash_reconciled',
      nextStage: 'completed',
      data: {
        amountPaid: paymentDetails.amountPaid,
        paymentMethod: paymentDetails.paymentMethod,
        referenceNo: paymentDetails.referenceNo,
      },
    });
  }

  // Audit log
  await prisma.auditLog.create({
    data: {
      action: 'payment_process',
      resource: 'sales_order',
      resourceId: orderId,
      details: {
        orderNumber: order.orderNumber,
        amountPaid: paymentDetails.amountPaid,
        paymentMethod: paymentDetails.paymentMethod,
        referenceNo: paymentDetails.referenceNo,
        customerName: order.customer.name,
        totalTimeMs: Date.now() - startTime,
        source: 'crm-pipeline',
      },
      success: true,
    },
  });

  return { success: true, orderNumber: order.orderNumber };
}

/**
 * Get the full audit trail for a CRM order — combines AgentAction + AuditLog
 */
export async function getCrmOrderAuditTrail(orderId: string) {
  const [agentActions, auditLogs, execution] = await Promise.all([
    prisma.agentAction.findMany({
      where: {
        OR: [
          { targetId: orderId },
          { executionId: { startsWith: orderId } },
        ],
      },
      orderBy: { createdAt: 'asc' },
    }),
    prisma.auditLog.findMany({
      where: { resourceId: orderId },
      orderBy: { timestamp: 'asc' },
    }),
    prisma.orderToCashExecution.findUnique({
      where: { orderId },
    }),
  ]);

  return {
    execution,
    agentActions,
    auditLogs,
    stageHistory: execution?.stageHistory || [],
    totalSteps: agentActions.length + auditLogs.length,
  };
}
