/**
 * Quotation Penalties Service
 * Apply late-payment penalties to overdue quotations based on user settings
 */

import { prisma } from '@/lib/db';
import { quotationSettingsAdapter } from '@/lib/adapters/quotations';
import { logger } from '@/lib/logger';
import { createId } from '@paralleldrive/cuid2';

const log = logger.scope('QuotationPenaltiesService');

// ═══════════════════════════════════════════════════════════════════════
// Apply Penalty
// ═══════════════════════════════════════════════════════════════════════

interface PenaltySettings {
  penaltyType: 'PERCENTAGE' | 'FLAT';
  penaltyRate: number;
  penaltyCompound: boolean;
  penaltyMaxAmount: number | null;
  penaltyFrequencyDays: number;
}

export async function applyPenalty(
  quotationId: string,
  settings: PenaltySettings,
): Promise<void> {
  const quotation = await prisma.quotations.findUnique({
    where: { id: quotationId },
    include: { penalties: { orderBy: { appliedAt: 'desc' }, take: 1 } },
  });

  if (!quotation) throw new Error(`Quotation not found: ${quotationId}`);

  // Check frequency - don't re-apply too soon since last penalty
  if (quotation.penalties.length > 0) {
    const lastPenalty = quotation.penalties[0];
    const daysSinceLastPenalty = Math.floor(
      (Date.now() - lastPenalty.appliedAt.getTime()) / (1000 * 60 * 60 * 24),
    );
    if (daysSinceLastPenalty < settings.penaltyFrequencyDays) {
      log.info('Penalty skipped - too soon since last penalty', {
        quotationId,
        daysSinceLastPenalty,
        frequencyDays: settings.penaltyFrequencyDays,
      });
      return;
    }
  }

  // Calculate penalty amount
  let amount: number;
  const base = settings.penaltyCompound
    ? quotation.total + quotation.penaltyAmount
    : quotation.total;

  if (settings.penaltyType === 'PERCENTAGE') {
    amount = Math.round(base * (settings.penaltyRate / 100) * 100) / 100;
  } else {
    amount = Math.round(settings.penaltyRate * 100) / 100;
  }

  // Apply max cap
  if (settings.penaltyMaxAmount !== null) {
    const totalAfterPenalty = quotation.penaltyAmount + amount;
    if (totalAfterPenalty > settings.penaltyMaxAmount) {
      amount = Math.max(0, settings.penaltyMaxAmount - quotation.penaltyAmount);
    }
    if (amount <= 0) {
      log.info('Penalty skipped - max amount reached', {
        quotationId,
        currentPenalty: quotation.penaltyAmount,
        maxAmount: settings.penaltyMaxAmount,
      });
      return;
    }
  }

  // Calculate days overdue
  const daysOverdue = quotation.dueDate
    ? Math.floor((Date.now() - quotation.dueDate.getTime()) / (1000 * 60 * 60 * 24))
    : 0;

  const penaltyId = createId();

  await prisma.$transaction(async (tx) => {
    // Create penalty audit row
    await tx.quotation_penalties.create({
      data: {
        id: penaltyId,
        quotationId,
        amount,
        penaltyType: settings.penaltyType,
        rate: settings.penaltyType === 'PERCENTAGE' ? settings.penaltyRate : null,
        flatAmount: settings.penaltyType === 'FLAT' ? settings.penaltyRate : null,
        isCompound: settings.penaltyCompound,
        daysOverdue,
        appliedAt: new Date(),
      },
    });

    // Update quotation penalty totals
    const newPenaltyAmount = Math.round((quotation.penaltyAmount + amount) * 100) / 100;

    // Recalculate payment status
    const totalWithPenalty = quotation.total + newPenaltyAmount;
    let paymentStatus: 'UNPAID' | 'PARTIAL' | 'PAID' | 'OVERDUE' = 'OVERDUE';
    if (quotation.amountPaid >= totalWithPenalty) {
      paymentStatus = 'PAID';
    } else if (quotation.amountPaid > 0) {
      paymentStatus = 'PARTIAL';
    }

    await tx.quotations.update({
      where: { id: quotationId },
      data: {
        penaltyAmount: newPenaltyAmount,
        penaltyAppliedAt: new Date(),
        paymentStatus,
        updatedAt: new Date(),
      },
    });
  });

  log.info('Penalty applied to quotation', {
    quotationId,
    penaltyId,
    amount,
    type: settings.penaltyType,
    daysOverdue,
  });
}

/**
 * Apply penalty by quotation ID - convenience wrapper for job handler
 */
export async function applyPenaltyById(quotationId: string): Promise<void> {
  const quotation = await prisma.quotations.findUnique({
    where: { id: quotationId },
    select: { userId: true },
  });

  if (!quotation) throw new Error(`Quotation not found: ${quotationId}`);

  const settings = await quotationSettingsAdapter.findUnique({
    where: { userId: quotation.userId },
  });

  if (!settings?.penaltyEnabled) return;

  await applyPenalty(quotationId, {
    penaltyType: settings.penaltyType,
    penaltyRate: settings.penaltyRate,
    penaltyCompound: settings.penaltyCompound,
    penaltyMaxAmount: settings.penaltyMaxAmount,
    penaltyFrequencyDays: settings.penaltyFrequencyDays,
  });
}

// ═══════════════════════════════════════════════════════════════════════
// Process Overdue Penalties
// ═══════════════════════════════════════════════════════════════════════

export async function processOverduePenalties(): Promise<{ processed: number; penalized: number; skipped: number }> {
  const now = new Date();

  // Find quotations that are overdue and unpaid/partial
  const quotations = await prisma.quotations.findMany({
    where: {
      paymentStatus: { in: ['UNPAID', 'PARTIAL'] },
      dueDate: { lt: now },
    },
    select: {
      id: true,
      userId: true,
      dueDate: true,
    },
  });

  let penalized = 0;
  let skipped = 0;

  for (const quotation of quotations) {
    if (!quotation.dueDate) {
      skipped++;
      continue;
    }

    // Get user settings
    const settings = await quotationSettingsAdapter.findUnique({
      where: { userId: quotation.userId },
    });

    if (!settings?.penaltyEnabled) {
      skipped++;
      continue;
    }

    // Check grace period
    const daysPastDue = Math.floor(
      (now.getTime() - quotation.dueDate.getTime()) / (1000 * 60 * 60 * 24),
    );

    if (daysPastDue <= settings.penaltyGraceDays) {
      skipped++;
      continue;
    }

    // Apply penalty
    try {
      await applyPenalty(quotation.id, {
        penaltyType: settings.penaltyType,
        penaltyRate: settings.penaltyRate,
        penaltyCompound: settings.penaltyCompound,
        penaltyMaxAmount: settings.penaltyMaxAmount,
        penaltyFrequencyDays: settings.penaltyFrequencyDays,
      });
      penalized++;
    } catch (error) {
      log.error('Failed to apply penalty', { quotationId: quotation.id, error });
      skipped++;
    }
  }

  log.info('Processed overdue penalties', { processed: quotations.length, penalized, skipped });
  return { processed: quotations.length, penalized, skipped };
}
