import { Injectable } from '@nestjs/common';
import { Invoice, InvoiceStatus, InvoiceCurrency } from '../entities/invoice.entity';
import { InvoiceItem, InvoiceItemType } from '../entities/invoice-item.entity';
import { Subscription } from '../entities/subscription.entity';
import { Plan } from '../entities/plan.entity';
import { UsageAggregate } from '../entities/usage-aggregate.entity';
import { OverageCalculatorService } from './overage-calculator.service';

@Injectable()
export class InvoiceGeneratorService {
  constructor(private readonly overageCalculator: OverageCalculatorService) {}

  generateMonthlyInvoice(
    subscription: Subscription,
    plan: Plan,
    usage: UsageAggregate,
    currency: InvoiceCurrency = InvoiceCurrency.USD,
    invoiceNumber: string,
  ): { invoice: Invoice; items: InvoiceItem[] } {
    const invoice = new Invoice();
    invoice.tenantId = subscription.tenantId;
    invoice.subscriptionId = subscription.id;
    invoice.invoiceNumber = invoiceNumber;
    invoice.status = InvoiceStatus.OPEN;
    invoice.currency = currency;
    invoice.periodStart = usage.periodStart ?? new Date(usage.year, usage.month - 1, 1);
    invoice.periodEnd = usage.periodEnd ?? new Date(usage.year, usage.month, 0, 23, 59, 59);
    invoice.dueDate = this.addDays(new Date(), 14);

    const items: InvoiceItem[] = [];

    // 1. Base subscription fee
    const baseAmountCents =
      currency === InvoiceCurrency.CNY ? plan.monthlyPriceFenCny : plan.monthlyPriceCentsUsd;

    if (baseAmountCents > 0) {
      const subItem = new InvoiceItem();
      subItem.itemType = InvoiceItemType.SUBSCRIPTION;
      subItem.description = `${plan.displayName} subscription (${this.formatPeriod(invoice.periodStart, invoice.periodEnd)})`;
      subItem.quantity = 1;
      subItem.unitPrice = baseAmountCents;
      subItem.amount = baseAmountCents;
      subItem.currency = currency;
      items.push(subItem);
    }

    // 2. Overage charges (token usage beyond plan quota)
    if (plan.overageRateCentsPerMTokenUsd > 0 && usage.totalTokens > plan.includedTokens) {
      const overageResult = this.overageCalculator.calculate(
        usage.totalTokens,
        plan.includedTokens,
        plan.overageRateCentsPerMTokenUsd,
      );

      if (overageResult.overageCentsUsd > 0) {
        const overageItem = new InvoiceItem();
        overageItem.itemType = InvoiceItemType.OVERAGE;
        overageItem.description = `Token overage: ${this.formatTokens(overageResult.overageTokens)} tokens × ${this.formatRate(plan.overageRateCentsPerMTokenUsd, currency)}/MTok`;
        overageItem.quantity = overageResult.overageTokens;
        overageItem.unitPrice = plan.overageRateCentsPerMTokenUsd; // per million tokens
        overageItem.amount = overageResult.overageCentsUsd;
        overageItem.currency = currency;
        items.push(overageItem);
      }
    }

    // Calculate totals
    const subtotal = items.reduce((sum, item) => sum + item.amount, 0);
    invoice.subtotalCents = subtotal;
    invoice.taxCents = 0; // Tax calculation handled separately
    invoice.totalCents = subtotal + invoice.taxCents;
    invoice.amountDueCents = invoice.totalCents;

    return { invoice, items };
  }

  /**
   * Generate a proration invoice item for mid-cycle upgrades.
   * Calculates the pro-rated difference between old and new plan for remaining days.
   */
  generateProratedUpgradeItem(
    oldPlan: Plan,
    newPlan: Plan,
    upgradeDate: Date,
    periodEnd: Date,
    currency: InvoiceCurrency,
  ): InvoiceItem {
    const totalDays = this.daysBetween(
      new Date(periodEnd.getFullYear(), periodEnd.getMonth() - 1, 1),
      periodEnd,
    );
    const remainingDays = this.daysBetween(upgradeDate, periodEnd);
    const fraction = remainingDays / totalDays;

    const oldPrice = currency === InvoiceCurrency.CNY ? oldPlan.monthlyPriceFenCny : oldPlan.monthlyPriceCentsUsd;
    const newPrice = currency === InvoiceCurrency.CNY ? newPlan.monthlyPriceFenCny : newPlan.monthlyPriceCentsUsd;
    const proratedAmount = Math.round((newPrice - oldPrice) * fraction);

    const item = new InvoiceItem();
    item.itemType = InvoiceItemType.ADJUSTMENT;
    item.description = `Plan upgrade proration: ${oldPlan.displayName} → ${newPlan.displayName} (${remainingDays}/${totalDays} days remaining)`;
    item.quantity = 1;
    item.unitPrice = proratedAmount;
    item.amount = proratedAmount;
    item.currency = currency;
    return item;
  }

  private addDays(date: Date, days: number): Date {
    const d = new Date(date);
    d.setDate(d.getDate() + days);
    return d;
  }

  private daysBetween(start: Date, end: Date): number {
    const ms = end.getTime() - start.getTime();
    return Math.ceil(ms / (1000 * 60 * 60 * 24));
  }

  private formatPeriod(start: Date, _end: Date): string {
    return `${start.toISOString().slice(0, 7)}`;
  }

  private formatTokens(tokens: number): string {
    if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(2)}M`;
    if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(0)}K`;
    return `${tokens}`;
  }

  private formatRate(centsPerMToken: number, currency: InvoiceCurrency): string {
    const symbol = currency === InvoiceCurrency.CNY ? '¥' : '$';
    return `${symbol}${(centsPerMToken / 100).toFixed(2)}`;
  }
}
