/**
 * Billing Calculation Service
 * Calculates billing amounts, overages, and generates invoices
 *
 * @module BillingUsage
 */

import { Repository } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import { TenantSubscription } from '../entities/tenant-subscription.entity';
import { SubscriptionPlan } from '../entities/subscription-plan.entity';
import { PlanLimit } from '../entities/plan-limit.entity';
import { UsageTracking } from '../entities/usage-tracking.entity';
import { Invoice } from '../../invoices/entities/invoice.entity';
import { InvoiceItem as BillingInvoiceItem } from '../entities/invoice-item.entity';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface BillingLineItem {
  description: string;
  itemType: 'subscription' | 'user' | 'profile' | 'overage' | 'addon';
  quantity: number;
  unitPrice: number;
  subtotal: number;
  profileCode?: string;
  platform?: string;
  metadata?: Record<string, any>;
}

export interface BillingCalculation {
  subscriptionAmount: number;
  userCharges: number;
  overageCharges: number;
  discounts: number;
  subtotal: number;
  taxAmount: number;
  total: number;
  lineItems: BillingLineItem[];
  periodStart: Date;
  periodEnd: Date;
}

export interface UsageLimitCheck {
  limitKey: string;
  limitName: string;
  limit: number;
  used: number;
  remaining: number;
  percentUsed: number;
  overageAmount: number;
  overageCost: number;
}

export class BillingCalculationService {
  private subscriptionRepository: Repository<TenantSubscription>;
  private planRepository: Repository<SubscriptionPlan>;
  private limitRepository: Repository<PlanLimit>;
  private usageRepository: Repository<UsageTracking>;
  private invoiceRepository: Repository<Invoice>;
  private invoiceItemRepository: Repository<BillingInvoiceItem>;

  constructor() {
    this.subscriptionRepository = AppDataSource.getRepository(TenantSubscription);
    this.planRepository = AppDataSource.getRepository(SubscriptionPlan);
    this.limitRepository = AppDataSource.getRepository(PlanLimit);
    this.usageRepository = AppDataSource.getRepository(UsageTracking);
    this.invoiceRepository = AppDataSource.getRepository(Invoice);
    this.invoiceItemRepository = AppDataSource.getRepository(BillingInvoiceItem);
  }

  async calculateBilling(ctx: ServiceContext): Promise<BillingCalculation | null> {
    const subscription = await this.subscriptionRepository.findOne({
      where: { tenantId: ctx.tenantId },
      relations: ['plan'],
    });

    if (!subscription) {
      return null;
    }

    const plan = subscription.plan;
    const lineItems: BillingLineItem[] = [];

    // Base subscription amount
    const subscriptionAmount = Number(subscription.currentPrice);
    lineItems.push({
      description: `${plan.name} - ${subscription.billingCycle === 'annual' ? 'Annual' : 'Monthly'} Subscription`,
      itemType: 'subscription',
      quantity: 1,
      unitPrice: subscriptionAmount,
      subtotal: subscriptionAmount,
    });

    // Check for user overages
    const usage = await this.usageRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        periodStart: subscription.currentPeriodStart,
      },
    });

    let userCharges = 0;
    let overageCharges = 0;

    if (usage) {
      // Calculate overage charges
      const limits = await this.limitRepository.find({
        where: { planId: plan.id },
      });

      for (const limit of limits) {
        const check = await this.checkUsageLimit(ctx, limit, usage);
        if (check.overageCost > 0 && limit.allowOverage) {
          overageCharges += check.overageCost;
          lineItems.push({
            description: `${limit.limitName} overage (${check.overageAmount} units)`,
            itemType: 'overage',
            quantity: check.overageAmount,
            unitPrice: Number(limit.overageUnitPrice),
            subtotal: check.overageCost,
            metadata: { limitKey: limit.limitKey },
          });
        }
      }
    }

    // Apply discounts
    let discounts = 0;
    if (subscription.discountPercent > 0) {
      discounts = (subscriptionAmount + userCharges + overageCharges) *
        (Number(subscription.discountPercent) / 100);
    }

    const subtotal = subscriptionAmount + userCharges + overageCharges - discounts;

    // Calculate tax (16% IVA for Mexico)
    const taxRate = 0.16;
    const taxAmount = subtotal * taxRate;

    const total = subtotal + taxAmount;

    return {
      subscriptionAmount,
      userCharges,
      overageCharges,
      discounts,
      subtotal,
      taxAmount,
      total: Math.round(total * 100) / 100,
      lineItems,
      periodStart: subscription.currentPeriodStart,
      periodEnd: subscription.currentPeriodEnd,
    };
  }

  async checkAllLimits(ctx: ServiceContext): Promise<UsageLimitCheck[]> {
    const subscription = await this.subscriptionRepository.findOne({
      where: { tenantId: ctx.tenantId },
    });

    if (!subscription) {
      return [];
    }

    const limits = await this.limitRepository.find({
      where: { planId: subscription.planId },
    });

    const usage = await this.usageRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        periodStart: subscription.currentPeriodStart,
      },
    });

    if (!usage) {
      return limits.map((limit) => ({
        limitKey: limit.limitKey,
        limitName: limit.limitName,
        limit: limit.limitValue,
        used: 0,
        remaining: limit.limitValue,
        percentUsed: 0,
        overageAmount: 0,
        overageCost: 0,
      }));
    }

    const checks: UsageLimitCheck[] = [];

    for (const limit of limits) {
      checks.push(await this.checkUsageLimit(ctx, limit, usage));
    }

    return checks;
  }

  private async checkUsageLimit(
    _ctx: ServiceContext,
    limit: PlanLimit,
    usage: UsageTracking
  ): Promise<UsageLimitCheck> {
    let used = 0;

    // Map limit keys to usage fields
    switch (limit.limitKey) {
      case 'users':
        used = usage.activeUsers || 0;
        break;
      case 'branches':
        used = usage.activeBranches || 0;
        break;
      case 'storage_gb':
        used = Number(usage.storageUsedGb) || 0;
        break;
      case 'api_calls':
        used = usage.apiCalls || 0;
        break;
      case 'documents':
        used = usage.documentsCount || 0;
        break;
      case 'sales':
        used = usage.salesCount || 0;
        break;
      case 'invoices':
        used = usage.invoicesGenerated || 0;
        break;
      case 'mobile_sessions':
        used = usage.mobileSessions || 0;
        break;
      default:
        used = 0;
    }

    const remaining = Math.max(0, limit.limitValue - used);
    const percentUsed = limit.limitValue > 0 ? (used / limit.limitValue) * 100 : 0;
    const overageAmount = Math.max(0, used - limit.limitValue);
    const overageCost = limit.allowOverage
      ? overageAmount * Number(limit.overageUnitPrice)
      : 0;

    return {
      limitKey: limit.limitKey,
      limitName: limit.limitName,
      limit: limit.limitValue,
      used,
      remaining,
      percentUsed: Math.round(percentUsed * 100) / 100,
      overageAmount,
      overageCost: Math.round(overageCost * 100) / 100,
    };
  }

  async generateInvoice(ctx: ServiceContext): Promise<Invoice | null> {
    const calculation = await this.calculateBilling(ctx);
    if (!calculation) {
      return null;
    }

    const subscription = await this.subscriptionRepository.findOne({
      where: { tenantId: ctx.tenantId },
    });

    if (!subscription) {
      return null;
    }

    // Generate invoice number
    const invoiceNumber = await this.generateInvoiceNumber();

    // Create invoice
    const invoice = this.invoiceRepository.create({
      tenantId: ctx.tenantId,
      invoiceNumber,
      invoiceType: 'sale',
      invoiceContext: 'saas',
      subscriptionId: subscription.id,
      periodStart: calculation.periodStart,
      periodEnd: calculation.periodEnd,
      billingName: subscription.billingName,
      billingEmail: subscription.billingEmail,
      billingAddress: subscription.billingAddress,
      taxId: subscription.taxId,
      invoiceDate: new Date(),
      dueDate: this.calculateDueDate(14), // 14 days payment term
      currency: 'MXN',
      subtotal: calculation.subtotal,
      taxAmount: calculation.taxAmount,
      total: calculation.total,
      status: 'draft',
      paymentTermDays: 14,
    });

    const savedInvoice = await this.invoiceRepository.save(invoice);

    // Create invoice items
    for (const item of calculation.lineItems) {
      const invoiceItem = this.invoiceItemRepository.create({
        invoiceId: savedInvoice.id,
        description: item.description,
        itemType: item.itemType,
        quantity: item.quantity,
        unitPrice: item.unitPrice,
        subtotal: item.subtotal,
        profileCode: item.profileCode,
        platform: item.platform,
        periodStart: calculation.periodStart,
        periodEnd: calculation.periodEnd,
        metadata: item.metadata,
      });
      await this.invoiceItemRepository.save(invoiceItem);
    }

    return savedInvoice;
  }

  async getUpcomingCharges(ctx: ServiceContext): Promise<BillingCalculation | null> {
    return this.calculateBilling(ctx);
  }

  async estimatePlanChange(
    ctx: ServiceContext,
    newPlanId: string
  ): Promise<{
    currentPlan: SubscriptionPlan;
    newPlan: SubscriptionPlan;
    proratedCredit: number;
    newCharge: number;
    netAmount: number;
  } | null> {
    const subscription = await this.subscriptionRepository.findOne({
      where: { tenantId: ctx.tenantId },
      relations: ['plan'],
    });

    if (!subscription) {
      return null;
    }

    const newPlan = await this.planRepository.findOne({
      where: { id: newPlanId },
    });

    if (!newPlan) {
      return null;
    }

    const currentPlan = subscription.plan;
    const now = new Date();

    // Calculate days remaining in current period
    const periodEnd = new Date(subscription.currentPeriodEnd);
    const periodStart = new Date(subscription.currentPeriodStart);
    const totalDays = Math.ceil(
      (periodEnd.getTime() - periodStart.getTime()) / (1000 * 60 * 60 * 24)
    );
    const daysRemaining = Math.ceil(
      (periodEnd.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
    );

    // Calculate prorated credit
    const dailyRate = Number(subscription.currentPrice) / totalDays;
    const proratedCredit = dailyRate * daysRemaining;

    // Calculate new charge (prorated)
    const newPrice =
      subscription.billingCycle === 'annual' && newPlan.baseAnnualPrice
        ? newPlan.baseAnnualPrice
        : newPlan.baseMonthlyPrice;
    const newDailyRate = Number(newPrice) / totalDays;
    const newCharge = newDailyRate * daysRemaining;

    const netAmount = newCharge - proratedCredit;

    return {
      currentPlan,
      newPlan,
      proratedCredit: Math.round(proratedCredit * 100) / 100,
      newCharge: Math.round(newCharge * 100) / 100,
      netAmount: Math.round(netAmount * 100) / 100,
    };
  }

  async getBillingHistory(
    ctx: ServiceContext,
    limit: number = 12
  ): Promise<Invoice[]> {
    return this.invoiceRepository.find({
      where: {
        tenantId: ctx.tenantId,
        invoiceContext: 'saas',
      },
      order: { invoiceDate: 'DESC' },
      take: limit,
    });
  }

  async getMonthlyRecurringRevenue(): Promise<number> {
    const subscriptions = await this.subscriptionRepository.find({
      where: { status: 'active' },
    });

    let mrr = 0;
    for (const sub of subscriptions) {
      if (sub.billingCycle === 'annual') {
        mrr += Number(sub.currentPrice) / 12;
      } else {
        mrr += Number(sub.currentPrice);
      }
    }

    return Math.round(mrr * 100) / 100;
  }

  async getAnnualRecurringRevenue(): Promise<number> {
    const mrr = await this.getMonthlyRecurringRevenue();
    return Math.round(mrr * 12 * 100) / 100;
  }

  private async generateInvoiceNumber(): Promise<string> {
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');

    // Get count of invoices this month
    const count = await this.invoiceRepository.count({
      where: {
        invoiceContext: 'saas',
      },
    });

    const sequence = String(count + 1).padStart(5, '0');
    return `SAAS-${year}${month}-${sequence}`;
  }

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