import { Injectable, Logger } from '@nestjs/common';
import { PlanRepository } from '../../infrastructure/repositories/plan.repository';
import { SubscriptionRepository } from '../../infrastructure/repositories/subscription.repository';
import { InvoiceRepository } from '../../infrastructure/repositories/invoice.repository';
import { UsageAggregateRepository } from '../../infrastructure/repositories/usage-aggregate.repository';
import { InvoiceGeneratorService } from '../../domain/services/invoice-generator.service';
import { SubscriptionLifecycleService } from '../../domain/services/subscription-lifecycle.service';
import { InvoiceCurrency } from '../../domain/entities/invoice.entity';
import { SubscriptionStatus } from '../../domain/entities/subscription.entity';

@Injectable()
export class GenerateMonthlyInvoiceUseCase {
  private readonly logger = new Logger(GenerateMonthlyInvoiceUseCase.name);

  constructor(
    private readonly planRepo: PlanRepository,
    private readonly subscriptionRepo: SubscriptionRepository,
    private readonly invoiceRepo: InvoiceRepository,
    private readonly usageAggRepo: UsageAggregateRepository,
    private readonly invoiceGenerator: InvoiceGeneratorService,
    private readonly lifecycle: SubscriptionLifecycleService,
  ) {}

  /**
   * Called by cron on the 1st of each month.
   * Generates invoices for the previous month and renews active subscriptions.
   */
  async execute(): Promise<void> {
    const now = new Date();
    const prevMonth = now.getMonth() === 0 ? 12 : now.getMonth();
    const prevYear = now.getMonth() === 0 ? now.getFullYear() - 1 : now.getFullYear();

    this.logger.log(`Generating monthly invoices for ${prevYear}-${String(prevMonth).padStart(2, '0')}`);

    // Find all subscriptions that expired (period end passed) or are active with cancelAtPeriodEnd
    const expired = await this.subscriptionRepo.findExpired();
    const cancelAtEnd = await this.subscriptionRepo.findCancelAtPeriodEnd();

    // Process expired subscriptions
    for (const sub of expired) {
      try {
        await this.processSubscription(sub, prevYear, prevMonth);
      } catch (err) {
        this.logger.error(`Failed to process subscription ${sub.id} for tenant ${sub.tenantId}: ${err.message}`);
      }
    }

    // Process cancellations
    for (const sub of cancelAtEnd) {
      try {
        if (sub.currentPeriodEnd <= now) {
          const updated = this.lifecycle.cancel(sub, true);
          await this.subscriptionRepo.save(updated);
          this.logger.log(`Cancelled subscription for tenant ${sub.tenantId} at period end`);
        }
      } catch (err) {
        this.logger.error(`Failed to cancel subscription ${sub.id}: ${err.message}`);
      }
    }
  }

  private async processSubscription(sub: any, year: number, month: number): Promise<void> {
    const plan = await this.planRepo.findById(sub.planId);
    if (!plan) {
      this.logger.warn(`Plan ${sub.planId} not found for subscription ${sub.id}`);
      return;
    }

    const usage = await this.usageAggRepo.findByTenantAndPeriod(sub.tenantId, year, month);
    if (!usage) {
      this.logger.debug(`No usage data for tenant ${sub.tenantId} in ${year}-${month}`);
    }

    // Only generate invoice if plan costs money
    if (plan.monthlyPriceCentsUsd > 0 || (usage && usage.totalTokens > plan.includedTokens)) {
      const invoiceNumber = await this.invoiceRepo.getNextInvoiceNumber();
      const { invoice, items } = this.invoiceGenerator.generateMonthlyInvoice(
        sub,
        plan,
        usage ?? this.emptyUsage(sub.tenantId, year, month),
        InvoiceCurrency.USD,
        invoiceNumber,
      );

      await this.invoiceRepo.saveWithItems(invoice, items);
      this.logger.log(`Generated invoice ${invoiceNumber} for tenant ${sub.tenantId}`);
    }

    // Renew the subscription (apply scheduled downgrade if any)
    if (sub.status === SubscriptionStatus.ACTIVE) {
      const renewed = this.lifecycle.renew(sub);
      await this.subscriptionRepo.save(renewed);
    } else if (sub.status === SubscriptionStatus.TRIALING && sub.trialEndsAt <= new Date()) {
      // Trial expired: activate if paid plan, else keep on free
      if (plan.monthlyPriceCentsUsd === 0) {
        const activated = this.lifecycle.activate(sub);
        await this.subscriptionRepo.save(activated);
      } else {
        // Mark as past_due until payment received
        const pastDue = this.lifecycle.markPastDue(sub);
        await this.subscriptionRepo.save(pastDue);
      }
    }
  }

  private emptyUsage(tenantId: string, year: number, month: number): any {
    return {
      tenantId,
      year,
      month,
      periodStart: new Date(year, month - 1, 1),
      periodEnd: new Date(year, month, 0, 23, 59, 59),
      totalInputTokens: 0,
      totalOutputTokens: 0,
      totalTokens: 0,
      totalCostUsd: 0,
      taskCount: 0,
    };
  }
}
