/**
 * Invoices Service
 *
 * Service for managing invoices
 */

import { Repository, DataSource, MoreThanOrEqual, LessThanOrEqual } from 'typeorm';
import { Invoice, InvoiceItem, InvoiceStatus, TenantSubscription, UsageTracking } from '../entities';
import {
  CreateInvoiceDto,
  UpdateInvoiceDto,
  RecordPaymentDto,
  VoidInvoiceDto,
  RefundInvoiceDto,
  GenerateInvoiceDto,
  InvoiceFilterDto,
} from '../dto';

export class InvoicesService {
  private invoiceRepository: Repository<Invoice>;
  private itemRepository: Repository<InvoiceItem>;
  private subscriptionRepository: Repository<TenantSubscription>;
  private usageRepository: Repository<UsageTracking>;

  constructor(private dataSource: DataSource) {
    this.invoiceRepository = dataSource.getRepository(Invoice);
    this.itemRepository = dataSource.getRepository(InvoiceItem);
    this.subscriptionRepository = dataSource.getRepository(TenantSubscription);
    this.usageRepository = dataSource.getRepository(UsageTracking);
  }

  /**
   * Create invoice manually
   */
  async create(dto: CreateInvoiceDto): Promise<Invoice> {
    const invoiceNumber = await this.generateInvoiceNumber();

    // Calculate totals
    let subtotal = 0;
    for (const item of dto.items) {
      const itemTotal = item.quantity * item.unitPrice;
      const discount = itemTotal * ((item.discountPercent || 0) / 100);
      subtotal += itemTotal - discount;
    }

    const taxAmount = subtotal * 0.16; // 16% IVA for Mexico
    const total = subtotal + taxAmount;

    const invoice = this.invoiceRepository.create({
      tenantId: dto.tenantId,
      subscriptionId: dto.subscriptionId,
      invoiceNumber,
      invoiceDate: dto.invoiceDate || new Date(),
      periodStart: dto.periodStart,
      periodEnd: dto.periodEnd,
      billingName: dto.billingName,
      billingEmail: dto.billingEmail,
      billingAddress: dto.billingAddress || {},
      taxId: dto.taxId,
      subtotal,
      taxAmount,
      discountAmount: 0,
      total,
      currency: dto.currency || 'MXN',
      status: 'draft',
      dueDate: dto.dueDate,
      notes: dto.notes,
      internalNotes: dto.internalNotes,
    });

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

    // Create items
    for (const itemDto of dto.items) {
      const itemTotal = itemDto.quantity * itemDto.unitPrice;
      const discount = itemTotal * ((itemDto.discountPercent || 0) / 100);

      const item = this.itemRepository.create({
        itemType: itemDto.itemType,
        description: itemDto.description,
        quantity: itemDto.quantity,
        unitPrice: itemDto.unitPrice,
        subtotal: itemTotal - discount,
        metadata: itemDto.metadata || {},
      });
      item.invoiceId = savedInvoice.id;

      await this.itemRepository.save(item);
    }

    return this.findById(savedInvoice.id) as Promise<Invoice>;
  }

  /**
   * Generate invoice automatically from subscription
   */
  async generateFromSubscription(dto: GenerateInvoiceDto): Promise<Invoice> {
    const subscription = await this.subscriptionRepository.findOne({
      where: { id: dto.subscriptionId },
      relations: ['plan'],
    });

    if (!subscription) {
      throw new Error('Subscription not found');
    }

    const items: CreateInvoiceDto['items'] = [];

    // Base subscription fee
    items.push({
      itemType: 'subscription',
      description: `Suscripcion ${subscription.plan.name} - ${subscription.billingCycle === 'annual' ? 'Anual' : 'Mensual'}`,
      quantity: 1,
      unitPrice: Number(subscription.currentPrice),
    });

    // Include usage charges if requested
    if (dto.includeUsageCharges) {
      const usage = await this.usageRepository.findOne({
        where: {
          tenantId: dto.tenantId,
          periodStart: dto.periodStart,
        },
      });

      if (usage) {
        // Extra users
        const extraUsers = Math.max(
          0,
          usage.activeUsers - (subscription.contractedUsers || subscription.plan.maxUsers)
        );
        if (extraUsers > 0) {
          items.push({
            itemType: 'overage',
            description: `Usuarios adicionales (${extraUsers})`,
            quantity: extraUsers,
            unitPrice: 10, // $10 per extra user
            metadata: { metric: 'extra_users' },
          });
        }

        // Extra branches
        const extraBranches = Math.max(
          0,
          usage.activeBranches - (subscription.contractedBranches || subscription.plan.maxBranches)
        );
        if (extraBranches > 0) {
          items.push({
            itemType: 'overage',
            description: `Sucursales adicionales (${extraBranches})`,
            quantity: extraBranches,
            unitPrice: 20, // $20 per extra branch
            metadata: { metric: 'extra_branches' },
          });
        }

        // Extra storage
        const extraStorageGb = Math.max(
          0,
          Number(usage.storageUsedGb) - subscription.plan.storageGb
        );
        if (extraStorageGb > 0) {
          items.push({
            itemType: 'overage',
            description: `Almacenamiento adicional (${extraStorageGb} GB)`,
            quantity: Math.ceil(extraStorageGb),
            unitPrice: 0.5, // $0.50 per GB
            metadata: { metric: 'extra_storage' },
          });
        }
      }
    }

    // Calculate due date (15 days from invoice date)
    const dueDate = new Date();
    dueDate.setDate(dueDate.getDate() + 15);

    return this.create({
      tenantId: dto.tenantId,
      subscriptionId: dto.subscriptionId,
      periodStart: dto.periodStart,
      periodEnd: dto.periodEnd,
      billingName: subscription.billingName,
      billingEmail: subscription.billingEmail,
      billingAddress: subscription.billingAddress,
      taxId: subscription.taxId,
      dueDate,
      items,
    });
  }

  /**
   * Find invoice by ID
   */
  async findById(id: string): Promise<Invoice | null> {
    return this.invoiceRepository.findOne({
      where: { id },
      relations: ['items'],
    });
  }

  /**
   * Find invoice by number
   */
  async findByNumber(invoiceNumber: string): Promise<Invoice | null> {
    return this.invoiceRepository.findOne({
      where: { invoiceNumber },
      relations: ['items'],
    });
  }

  /**
   * Find invoices with filters
   */
  async findAll(filter: InvoiceFilterDto): Promise<{ data: Invoice[]; total: number }> {
    const query = this.invoiceRepository
      .createQueryBuilder('invoice')
      .leftJoinAndSelect('invoice.items', 'items');

    if (filter.tenantId) {
      query.andWhere('invoice.tenantId = :tenantId', { tenantId: filter.tenantId });
    }

    if (filter.status) {
      query.andWhere('invoice.status = :status', { status: filter.status });
    }

    if (filter.dateFrom) {
      query.andWhere('invoice.invoiceDate >= :dateFrom', { dateFrom: filter.dateFrom });
    }

    if (filter.dateTo) {
      query.andWhere('invoice.invoiceDate <= :dateTo', { dateTo: filter.dateTo });
    }

    if (filter.overdue) {
      query.andWhere('invoice.dueDate < :now', { now: new Date() });
      query.andWhere("invoice.status IN ('sent', 'partial')");
    }

    const total = await query.getCount();

    query.orderBy('invoice.invoiceDate', 'DESC');

    if (filter.limit) {
      query.take(filter.limit);
    }

    if (filter.offset) {
      query.skip(filter.offset);
    }

    const data = await query.getMany();

    return { data, total };
  }

  /**
   * Update invoice
   */
  async update(id: string, dto: UpdateInvoiceDto): Promise<Invoice> {
    const invoice = await this.findById(id);
    if (!invoice) {
      throw new Error('Invoice not found');
    }

    if (invoice.status !== 'draft') {
      throw new Error('Only draft invoices can be updated');
    }

    Object.assign(invoice, dto);
    return this.invoiceRepository.save(invoice);
  }

  /**
   * Send invoice
   */
  async send(id: string): Promise<Invoice> {
    const invoice = await this.findById(id);
    if (!invoice) {
      throw new Error('Invoice not found');
    }

    if (invoice.status !== 'draft') {
      throw new Error('Only draft invoices can be sent');
    }

    invoice.status = 'sent';
    // TODO: Send email notification to billing email

    return this.invoiceRepository.save(invoice);
  }

  /**
   * Record payment
   */
  async recordPayment(id: string, dto: RecordPaymentDto): Promise<Invoice> {
    const invoice = await this.findById(id);
    if (!invoice) {
      throw new Error('Invoice not found');
    }

    if (invoice.status === 'void' || invoice.status === 'refunded') {
      throw new Error('Cannot record payment for voided or refunded invoice');
    }

    const newPaidAmount = Number(invoice.paidAmount) + dto.amount;
    const total = Number(invoice.total);

    invoice.paidAmount = newPaidAmount;
    invoice.paymentMethod = dto.paymentMethod;
    invoice.paymentReference = dto.paymentReference || '';

    if (newPaidAmount >= total) {
      invoice.status = 'paid';
      invoice.paidAt = dto.paymentDate || new Date();
    } else if (newPaidAmount > 0) {
      invoice.status = 'partial';
    }

    return this.invoiceRepository.save(invoice);
  }

  /**
   * Void invoice
   */
  async void(id: string, dto: VoidInvoiceDto): Promise<Invoice> {
    const invoice = await this.findById(id);
    if (!invoice) {
      throw new Error('Invoice not found');
    }

    if (invoice.status === 'paid' || invoice.status === 'refunded') {
      throw new Error('Cannot void paid or refunded invoice');
    }

    invoice.status = 'void';
    invoice.internalNotes = `${invoice.internalNotes || ''}\n\nVoided: ${dto.reason}`.trim();

    return this.invoiceRepository.save(invoice);
  }

  /**
   * Refund invoice
   */
  async refund(id: string, dto: RefundInvoiceDto): Promise<Invoice> {
    const invoice = await this.findById(id);
    if (!invoice) {
      throw new Error('Invoice not found');
    }

    if (invoice.status !== 'paid' && invoice.status !== 'partial') {
      throw new Error('Only paid invoices can be refunded');
    }

    const refundAmount = dto.amount || Number(invoice.paidAmount);

    if (refundAmount > Number(invoice.paidAmount)) {
      throw new Error('Refund amount cannot exceed paid amount');
    }

    invoice.status = 'refunded';
    invoice.internalNotes =
      `${invoice.internalNotes || ''}\n\nRefunded: ${refundAmount} - ${dto.reason}`.trim();

    // TODO: Process actual refund through payment provider

    return this.invoiceRepository.save(invoice);
  }

  /**
   * Mark overdue invoices
   */
  async markOverdueInvoices(): Promise<number> {
    const now = new Date();

    const result = await this.invoiceRepository
      .createQueryBuilder()
      .update(Invoice)
      .set({ status: 'overdue' })
      .where("status IN ('sent', 'partial')")
      .andWhere('dueDate < :now', { now })
      .execute();

    return result.affected || 0;
  }

  /**
   * Get invoice statistics
   */
  async getStats(tenantId?: string): Promise<{
    total: number;
    byStatus: Record<InvoiceStatus, number>;
    totalRevenue: number;
    pendingAmount: number;
    overdueAmount: number;
  }> {
    const query = this.invoiceRepository.createQueryBuilder('invoice');

    if (tenantId) {
      query.where('invoice.tenantId = :tenantId', { tenantId });
    }

    const invoices = await query.getMany();

    const byStatus: Record<InvoiceStatus, number> = {
      draft: 0,
      validated: 0,
      sent: 0,
      paid: 0,
      partial: 0,
      overdue: 0,
      void: 0,
      refunded: 0,
      cancelled: 0,
      voided: 0,
    };

    let totalRevenue = 0;
    let pendingAmount = 0;
    let overdueAmount = 0;
    const now = new Date();

    for (const invoice of invoices) {
      byStatus[invoice.status]++;

      if (invoice.status === 'paid') {
        totalRevenue += Number(invoice.paidAmount);
      }

      if (invoice.status === 'sent' || invoice.status === 'partial') {
        const pending = Number(invoice.total) - Number(invoice.paidAmount);
        pendingAmount += pending;

        if (invoice.dueDate && invoice.dueDate < now) {
          overdueAmount += pending;
        }
      }
    }

    return {
      total: invoices.length,
      byStatus,
      totalRevenue,
      pendingAmount,
      overdueAmount,
    };
  }

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

    // Get last invoice number for this month
    const lastInvoice = await this.invoiceRepository
      .createQueryBuilder('invoice')
      .where('invoice.invoiceNumber LIKE :pattern', { pattern: `INV-${year}${month}%` })
      .orderBy('invoice.invoiceNumber', 'DESC')
      .getOne();

    let sequence = 1;
    if (lastInvoice) {
      const lastSequence = parseInt(lastInvoice.invoiceNumber.slice(-4), 10);
      sequence = lastSequence + 1;
    }

    return `INV-${year}${month}-${String(sequence).padStart(4, '0')}`;
  }
}
