/**
 * QuotationService - Servicio de cotizaciones de venta
 *
 * Gestión de cotizaciones para venta de unidades inmobiliarias
 * (departamentos, lotes, viviendas).
 *
 * @module Sales (MAI-010)
 */

import { Repository, DataSource } from 'typeorm';
import { Quotation } from '../entities/quotation.entity';
import { QuotationItem } from '../entities/quotation-item.entity';

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

export interface CreateQuotationDto {
  partnerId: string;
  partnerName?: string;
  partnerEmail?: string;
  billingAddress?: object;
  shippingAddress?: object;
  quotationDate?: Date;
  validUntil?: Date;
  expectedCloseDate?: Date;
  salesRepId?: string;
  currency?: string;
  paymentTermDays?: number;
  paymentMethod?: string;
  notes?: string;
  internalNotes?: string;
  termsAndConditions?: string;
  items?: CreateQuotationItemDto[];
}

export interface UpdateQuotationDto {
  partnerName?: string;
  partnerEmail?: string;
  billingAddress?: object;
  shippingAddress?: object;
  validUntil?: Date;
  expectedCloseDate?: Date;
  salesRepId?: string;
  currency?: string;
  paymentTermDays?: number;
  paymentMethod?: string;
  notes?: string;
  internalNotes?: string;
  termsAndConditions?: string;
}

export interface CreateQuotationItemDto {
  productId?: string;
  productSku?: string;
  productName: string;
  description?: string;
  quantity: number;
  uom?: string;
  unitPrice: number;
  discountPercent?: number;
  taxRate?: number;
}

export interface UpdateQuotationItemDto {
  productName?: string;
  description?: string;
  quantity?: number;
  uom?: string;
  unitPrice?: number;
  discountPercent?: number;
  taxRate?: number;
}

export interface QuotationFilters {
  partnerId?: string;
  salesRepId?: string;
  status?: string;
  dateFrom?: Date;
  dateTo?: Date;
  minTotal?: number;
  maxTotal?: number;
  search?: string;
  page?: number;
  limit?: number;
}

export class QuotationService {
  private readonly quotationRepo: Repository<Quotation>;
  private readonly itemRepo: Repository<QuotationItem>;
  private readonly dataSource: DataSource;

  constructor(dataSource: DataSource) {
    this.dataSource = dataSource;
    this.quotationRepo = dataSource.getRepository(Quotation);
    this.itemRepo = dataSource.getRepository(QuotationItem);
  }

  async findAll(
    ctx: ServiceContext,
    filters: QuotationFilters = {},
  ): Promise<{ data: Quotation[]; total: number; page: number; limit: number }> {
    const page = filters.page || 1;
    const limit = filters.limit || 50;
    const skip = (page - 1) * limit;

    const queryBuilder = this.quotationRepo
      .createQueryBuilder('q')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('q.deleted_at IS NULL');

    if (filters.partnerId) {
      queryBuilder.andWhere('q.partner_id = :partnerId', { partnerId: filters.partnerId });
    }

    if (filters.salesRepId) {
      queryBuilder.andWhere('q.sales_rep_id = :salesRepId', { salesRepId: filters.salesRepId });
    }

    if (filters.status) {
      queryBuilder.andWhere('q.status = :status', { status: filters.status });
    }

    if (filters.dateFrom) {
      queryBuilder.andWhere('q.quotation_date >= :dateFrom', { dateFrom: filters.dateFrom });
    }

    if (filters.dateTo) {
      queryBuilder.andWhere('q.quotation_date <= :dateTo', { dateTo: filters.dateTo });
    }

    if (filters.minTotal !== undefined) {
      queryBuilder.andWhere('q.total >= :minTotal', { minTotal: filters.minTotal });
    }

    if (filters.maxTotal !== undefined) {
      queryBuilder.andWhere('q.total <= :maxTotal', { maxTotal: filters.maxTotal });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(q.quotation_number ILIKE :search OR q.partner_name ILIKE :search)',
        { search: `%${filters.search}%` },
      );
    }

    const [data, total] = await queryBuilder
      .orderBy('q.created_at', 'DESC')
      .skip(skip)
      .take(limit)
      .getManyAndCount();

    return { data, total, page, limit };
  }

  async findById(ctx: ServiceContext, id: string): Promise<Quotation | null> {
    return this.quotationRepo.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      },
    });
  }

  async findByIdWithItems(ctx: ServiceContext, id: string): Promise<Quotation | null> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return null;
    }

    const items = await this.itemRepo.find({
      where: { quotationId: id },
      order: { lineNumber: 'ASC' },
    });

    return { ...quotation, items } as Quotation & { items: QuotationItem[] };
  }

  async findByNumber(ctx: ServiceContext, quotationNumber: string): Promise<Quotation | null> {
    return this.quotationRepo.findOne({
      where: {
        quotationNumber,
        tenantId: ctx.tenantId,
      },
    });
  }

  async findByPartner(ctx: ServiceContext, partnerId: string): Promise<Quotation[]> {
    return this.quotationRepo
      .createQueryBuilder('q')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('q.deleted_at IS NULL')
      .andWhere('q.partner_id = :partnerId', { partnerId })
      .orderBy('q.created_at', 'DESC')
      .getMany();
  }

  async findPending(ctx: ServiceContext): Promise<Quotation[]> {
    return this.quotationRepo
      .createQueryBuilder('q')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('q.deleted_at IS NULL')
      .andWhere('q.status IN (:...statuses)', { statuses: ['draft', 'sent'] })
      .orderBy('q.created_at', 'DESC')
      .getMany();
  }

  async findExpired(ctx: ServiceContext): Promise<Quotation[]> {
    return this.quotationRepo
      .createQueryBuilder('q')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('q.deleted_at IS NULL')
      .andWhere('q.status IN (:...statuses)', { statuses: ['draft', 'sent'] })
      .andWhere('q.valid_until < CURRENT_DATE')
      .orderBy('q.valid_until', 'ASC')
      .getMany();
  }

  private async generateQuotationNumber(ctx: ServiceContext): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `COT-${year}-`;

    const lastQuotation = await this.quotationRepo
      .createQueryBuilder('q')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('q.quotation_number LIKE :prefix', { prefix: `${prefix}%` })
      .orderBy('q.quotation_number', 'DESC')
      .getOne();

    let sequence = 1;
    if (lastQuotation) {
      const lastNumber = lastQuotation.quotationNumber.replace(prefix, '');
      sequence = parseInt(lastNumber, 10) + 1;
    }

    return `${prefix}${sequence.toString().padStart(5, '0')}`;
  }

  private calculateItemTotals(item: CreateQuotationItemDto): {
    discountAmount: number;
    taxAmount: number;
    subtotal: number;
    total: number;
  } {
    const quantity = Number(item.quantity) || 0;
    const unitPrice = Number(item.unitPrice) || 0;
    const discountPercent = Number(item.discountPercent) || 0;
    const taxRate = Number(item.taxRate) ?? 16;

    const grossAmount = quantity * unitPrice;
    const discountAmount = grossAmount * (discountPercent / 100);
    const subtotal = grossAmount - discountAmount;
    const taxAmount = subtotal * (taxRate / 100);
    const total = subtotal + taxAmount;

    return {
      discountAmount: Math.round(discountAmount * 100) / 100,
      taxAmount: Math.round(taxAmount * 100) / 100,
      subtotal: Math.round(subtotal * 100) / 100,
      total: Math.round(total * 100) / 100,
    };
  }

  private calculateQuotationTotals(items: Array<{ subtotal: number; taxAmount: number; discountAmount: number }>): {
    subtotal: number;
    taxAmount: number;
    discountAmount: number;
    total: number;
  } {
    const subtotal = items.reduce((sum, item) => sum + Number(item.subtotal), 0);
    const taxAmount = items.reduce((sum, item) => sum + Number(item.taxAmount), 0);
    const discountAmount = items.reduce((sum, item) => sum + Number(item.discountAmount), 0);
    const total = subtotal + taxAmount;

    return {
      subtotal: Math.round(subtotal * 100) / 100,
      taxAmount: Math.round(taxAmount * 100) / 100,
      discountAmount: Math.round(discountAmount * 100) / 100,
      total: Math.round(total * 100) / 100,
    };
  }

  async create(ctx: ServiceContext, dto: CreateQuotationDto): Promise<Quotation> {
    const quotationNumber = await this.generateQuotationNumber(ctx);

    return this.dataSource.transaction(async (manager) => {
      const quotationRepo = manager.getRepository(Quotation);
      const itemRepo = manager.getRepository(QuotationItem);

      const quotation = quotationRepo.create({
        tenantId: ctx.tenantId,
        createdBy: ctx.userId,
        quotationNumber,
        partnerId: dto.partnerId,
        partnerName: dto.partnerName,
        partnerEmail: dto.partnerEmail,
        billingAddress: dto.billingAddress,
        shippingAddress: dto.shippingAddress,
        quotationDate: dto.quotationDate ?? new Date(),
        validUntil: dto.validUntil,
        expectedCloseDate: dto.expectedCloseDate,
        salesRepId: dto.salesRepId ?? ctx.userId,
        currency: dto.currency ?? 'MXN',
        paymentTermDays: dto.paymentTermDays ?? 0,
        paymentMethod: dto.paymentMethod,
        notes: dto.notes,
        internalNotes: dto.internalNotes,
        termsAndConditions: dto.termsAndConditions,
        status: 'draft',
        subtotal: 0,
        taxAmount: 0,
        discountAmount: 0,
        total: 0,
      });

      const savedQuotation = await quotationRepo.save(quotation);

      if (dto.items && dto.items.length > 0) {
        const itemEntities: QuotationItem[] = [];

        for (let i = 0; i < dto.items.length; i++) {
          const itemDto = dto.items[i];
          const calculated = this.calculateItemTotals(itemDto);

          const item = itemRepo.create({
            quotationId: savedQuotation.id,
            lineNumber: i + 1,
            productId: itemDto.productId,
            productSku: itemDto.productSku,
            productName: itemDto.productName,
            description: itemDto.description,
            quantity: itemDto.quantity,
            uom: itemDto.uom ?? 'PZA',
            unitPrice: itemDto.unitPrice,
            discountPercent: itemDto.discountPercent ?? 0,
            taxRate: itemDto.taxRate ?? 16,
            ...calculated,
          });

          itemEntities.push(item);
        }

        await itemRepo.save(itemEntities);

        const totals = this.calculateQuotationTotals(itemEntities);
        savedQuotation.subtotal = totals.subtotal;
        savedQuotation.taxAmount = totals.taxAmount;
        savedQuotation.discountAmount = totals.discountAmount;
        savedQuotation.total = totals.total;

        await quotationRepo.save(savedQuotation);
      }

      return savedQuotation;
    });
  }

  async update(ctx: ServiceContext, id: string, dto: UpdateQuotationDto): Promise<Quotation | null> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return null;
    }

    if (quotation.status === 'converted') {
      throw new Error('Cannot update a converted quotation');
    }

    Object.assign(quotation, {
      ...dto,
      updatedBy: ctx.userId,
    });

    return this.quotationRepo.save(quotation);
  }

  async addItem(ctx: ServiceContext, quotationId: string, dto: CreateQuotationItemDto): Promise<QuotationItem | null> {
    const quotation = await this.findById(ctx, quotationId);
    if (!quotation) {
      return null;
    }

    if (quotation.status === 'converted') {
      throw new Error('Cannot add items to a converted quotation');
    }

    return this.dataSource.transaction(async (manager) => {
      const itemRepo = manager.getRepository(QuotationItem);

      const lastItem = await itemRepo
        .createQueryBuilder('i')
        .where('i.quotation_id = :quotationId', { quotationId })
        .orderBy('i.line_number', 'DESC')
        .getOne();

      const lineNumber = (lastItem?.lineNumber ?? 0) + 1;
      const calculated = this.calculateItemTotals(dto);

      const item = itemRepo.create({
        quotationId,
        lineNumber,
        productId: dto.productId,
        productSku: dto.productSku,
        productName: dto.productName,
        description: dto.description,
        quantity: dto.quantity,
        uom: dto.uom ?? 'PZA',
        unitPrice: dto.unitPrice,
        discountPercent: dto.discountPercent ?? 0,
        taxRate: dto.taxRate ?? 16,
        ...calculated,
      });

      const savedItem = await itemRepo.save(item);

      await this.recalculateQuotationTotals(manager, quotationId);

      return savedItem;
    });
  }

  async updateItem(
    ctx: ServiceContext,
    quotationId: string,
    itemId: string,
    dto: UpdateQuotationItemDto,
  ): Promise<QuotationItem | null> {
    const quotation = await this.findById(ctx, quotationId);
    if (!quotation) {
      return null;
    }

    if (quotation.status === 'converted') {
      throw new Error('Cannot update items on a converted quotation');
    }

    const item = await this.itemRepo.findOne({
      where: { id: itemId, quotationId },
    });

    if (!item) {
      return null;
    }

    return this.dataSource.transaction(async (manager) => {
      const itemRepo = manager.getRepository(QuotationItem);

      Object.assign(item, dto);

      const calculated = this.calculateItemTotals({
        quantity: item.quantity,
        unitPrice: item.unitPrice,
        discountPercent: item.discountPercent,
        taxRate: item.taxRate,
        productName: item.productName,
      });

      Object.assign(item, calculated);

      const savedItem = await itemRepo.save(item);

      await this.recalculateQuotationTotals(manager, quotationId);

      return savedItem;
    });
  }

  async removeItem(ctx: ServiceContext, quotationId: string, itemId: string): Promise<boolean> {
    const quotation = await this.findById(ctx, quotationId);
    if (!quotation) {
      return false;
    }

    if (quotation.status === 'converted') {
      throw new Error('Cannot remove items from a converted quotation');
    }

    return this.dataSource.transaction(async (manager) => {
      const itemRepo = manager.getRepository(QuotationItem);

      const result = await itemRepo.delete({ id: itemId, quotationId });
      if (!result.affected) {
        return false;
      }

      await this.recalculateQuotationTotals(manager, quotationId);

      return true;
    });
  }

  private async recalculateQuotationTotals(
    manager: any,
    quotationId: string,
  ): Promise<void> {
    const quotationRepo = manager.getRepository(Quotation);
    const itemRepo = manager.getRepository(QuotationItem);

    const items = await itemRepo.find({
      where: { quotationId },
    });

    const totals = this.calculateQuotationTotals(items);

    await quotationRepo.update({ id: quotationId }, totals);
  }

  async send(ctx: ServiceContext, id: string): Promise<Quotation | null> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return null;
    }

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

    quotation.status = 'sent';
    quotation.updatedBy = ctx.userId;

    return this.quotationRepo.save(quotation);
  }

  async accept(ctx: ServiceContext, id: string): Promise<Quotation | null> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return null;
    }

    if (quotation.status !== 'sent' && quotation.status !== 'draft') {
      throw new Error('Only draft or sent quotations can be accepted');
    }

    quotation.status = 'accepted';
    quotation.updatedBy = ctx.userId;

    return this.quotationRepo.save(quotation);
  }

  async reject(ctx: ServiceContext, id: string): Promise<Quotation | null> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return null;
    }

    if (quotation.status !== 'sent' && quotation.status !== 'draft') {
      throw new Error('Only draft or sent quotations can be rejected');
    }

    quotation.status = 'rejected';
    quotation.updatedBy = ctx.userId;

    return this.quotationRepo.save(quotation);
  }

  async markExpired(ctx: ServiceContext, id: string): Promise<Quotation | null> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return null;
    }

    if (quotation.status === 'converted' || quotation.status === 'accepted') {
      throw new Error('Cannot expire a converted or accepted quotation');
    }

    quotation.status = 'expired';
    quotation.updatedBy = ctx.userId;

    return this.quotationRepo.save(quotation);
  }

  async markConverted(ctx: ServiceContext, id: string, orderId: string): Promise<Quotation | null> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return null;
    }

    if (quotation.status !== 'accepted') {
      throw new Error('Only accepted quotations can be converted to orders');
    }

    quotation.status = 'converted';
    quotation.convertedToOrder = true;
    quotation.orderId = orderId;
    quotation.convertedAt = new Date();
    quotation.updatedBy = ctx.userId;

    return this.quotationRepo.save(quotation);
  }

  async duplicate(ctx: ServiceContext, id: string): Promise<Quotation | null> {
    const original = await this.findByIdWithItems(ctx, id);
    if (!original) {
      return null;
    }

    const items = (original as any).items as QuotationItem[] || [];

    const newQuotation = await this.create(ctx, {
      partnerId: original.partnerId,
      partnerName: original.partnerName,
      partnerEmail: original.partnerEmail,
      billingAddress: original.billingAddress,
      shippingAddress: original.shippingAddress,
      salesRepId: original.salesRepId,
      currency: original.currency,
      paymentTermDays: original.paymentTermDays,
      paymentMethod: original.paymentMethod,
      notes: original.notes,
      internalNotes: original.internalNotes,
      termsAndConditions: original.termsAndConditions,
      items: items.map((item) => ({
        productId: item.productId,
        productSku: item.productSku,
        productName: item.productName,
        description: item.description,
        quantity: item.quantity,
        uom: item.uom,
        unitPrice: item.unitPrice,
        discountPercent: item.discountPercent,
        taxRate: item.taxRate,
      })),
    });

    return newQuotation;
  }

  async getStatistics(ctx: ServiceContext, dateFrom?: Date, dateTo?: Date): Promise<{
    totalQuotations: number;
    draft: number;
    sent: number;
    accepted: number;
    rejected: number;
    expired: number;
    converted: number;
    totalValue: number;
    acceptedValue: number;
    conversionRate: number;
  }> {
    const queryBuilder = this.quotationRepo
      .createQueryBuilder('q')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('q.deleted_at IS NULL');

    if (dateFrom) {
      queryBuilder.andWhere('q.quotation_date >= :dateFrom', { dateFrom });
    }

    if (dateTo) {
      queryBuilder.andWhere('q.quotation_date <= :dateTo', { dateTo });
    }

    const quotations = await queryBuilder.getMany();

    const draft = quotations.filter((q) => q.status === 'draft');
    const sent = quotations.filter((q) => q.status === 'sent');
    const accepted = quotations.filter((q) => q.status === 'accepted');
    const rejected = quotations.filter((q) => q.status === 'rejected');
    const expired = quotations.filter((q) => q.status === 'expired');
    const converted = quotations.filter((q) => q.status === 'converted');

    const totalValue = quotations.reduce((sum, q) => sum + Number(q.total), 0);
    const acceptedValue = [...accepted, ...converted].reduce((sum, q) => sum + Number(q.total), 0);

    const sentOrBetter = quotations.filter((q) =>
      q.status !== 'draft'
    ).length;

    const conversionRate = sentOrBetter > 0
      ? ((accepted.length + converted.length) / sentOrBetter) * 100
      : 0;

    return {
      totalQuotations: quotations.length,
      draft: draft.length,
      sent: sent.length,
      accepted: accepted.length,
      rejected: rejected.length,
      expired: expired.length,
      converted: converted.length,
      totalValue,
      acceptedValue,
      conversionRate: Math.round(conversionRate * 100) / 100,
    };
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const quotation = await this.findById(ctx, id);
    if (!quotation) {
      return false;
    }

    if (quotation.status === 'converted') {
      throw new Error('Cannot delete a converted quotation');
    }

    const result = await this.quotationRepo.update(
      { id, tenantId: ctx.tenantId },
      { deletedAt: new Date(), updatedBy: ctx.userId },
    );

    return (result.affected ?? 0) > 0;
  }
}
