/**
 * SupplierConstructionService - Servicio de extensión de proveedores
 *
 * Gestión de datos adicionales de proveedores para construcción.
 * Incluye evaluaciones, ratings y calificaciones.
 *
 * @module Purchase (MAI-004)
 */

import { Repository } from 'typeorm';
import { SupplierConstruction } from '../entities/supplier-construction.entity';

export interface CreateSupplierConstructionDto {
  supplierId: string;
  isMaterialsSupplier?: boolean;
  isServicesSupplier?: boolean;
  isEquipmentSupplier?: boolean;
  specialties?: string[];
  creditLimit?: number;
  paymentDays?: number;
  hasValidDocuments?: boolean;
  documentsExpiryDate?: Date;
}

export interface UpdateSupplierConstructionDto {
  isMaterialsSupplier?: boolean;
  isServicesSupplier?: boolean;
  isEquipmentSupplier?: boolean;
  specialties?: string[];
  creditLimit?: number;
  paymentDays?: number;
  hasValidDocuments?: boolean;
  documentsExpiryDate?: Date;
}

export interface EvaluateSupplierDto {
  qualityRating: number;
  deliveryRating: number;
  priceRating: number;
}

export interface SupplierConstructionFilters {
  isMaterialsSupplier?: boolean;
  isServicesSupplier?: boolean;
  isEquipmentSupplier?: boolean;
  minRating?: number;
  hasValidDocuments?: boolean;
  specialty?: string;
  page?: number;
  limit?: number;
}

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

export class SupplierConstructionService {
  constructor(
    private readonly repository: Repository<SupplierConstruction>,
  ) {}

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

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

    if (filters.isMaterialsSupplier !== undefined) {
      queryBuilder.andWhere('sc.is_materials_supplier = :isMaterials', {
        isMaterials: filters.isMaterialsSupplier,
      });
    }

    if (filters.isServicesSupplier !== undefined) {
      queryBuilder.andWhere('sc.is_services_supplier = :isServices', {
        isServices: filters.isServicesSupplier,
      });
    }

    if (filters.isEquipmentSupplier !== undefined) {
      queryBuilder.andWhere('sc.is_equipment_supplier = :isEquipment', {
        isEquipment: filters.isEquipmentSupplier,
      });
    }

    if (filters.minRating !== undefined) {
      queryBuilder.andWhere('sc.overall_rating >= :minRating', {
        minRating: filters.minRating,
      });
    }

    if (filters.hasValidDocuments !== undefined) {
      queryBuilder.andWhere('sc.has_valid_documents = :hasValid', {
        hasValid: filters.hasValidDocuments,
      });
    }

    if (filters.specialty) {
      queryBuilder.andWhere(':specialty = ANY(sc.specialties)', {
        specialty: filters.specialty,
      });
    }

    const [data, total] = await queryBuilder
      .orderBy('sc.overall_rating', 'DESC', 'NULLS LAST')
      .addOrderBy('sc.created_at', 'DESC')
      .skip(skip)
      .take(limit)
      .getManyAndCount();

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

  async findById(ctx: ServiceContext, id: string): Promise<SupplierConstruction | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      },
      relations: ['createdBy', 'updatedBy'],
    });
  }

  async findBySupplierId(ctx: ServiceContext, supplierId: string): Promise<SupplierConstruction | null> {
    return this.repository.findOne({
      where: {
        supplierId,
        tenantId: ctx.tenantId,
      },
    });
  }

  async create(
    ctx: ServiceContext,
    dto: CreateSupplierConstructionDto,
  ): Promise<SupplierConstruction> {
    // Check if extension already exists for this supplier
    const existing = await this.findBySupplierId(ctx, dto.supplierId);
    if (existing) {
      throw new Error('Construction extension already exists for this supplier');
    }

    const extension = this.repository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      supplierId: dto.supplierId,
      isMaterialsSupplier: dto.isMaterialsSupplier ?? false,
      isServicesSupplier: dto.isServicesSupplier ?? false,
      isEquipmentSupplier: dto.isEquipmentSupplier ?? false,
      specialties: dto.specialties,
      creditLimit: dto.creditLimit,
      paymentDays: dto.paymentDays ?? 30,
      hasValidDocuments: dto.hasValidDocuments ?? false,
      documentsExpiryDate: dto.documentsExpiryDate,
    });

    return this.repository.save(extension);
  }

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

    Object.assign(extension, {
      ...dto,
      updatedById: ctx.userId,
    });

    return this.repository.save(extension);
  }

  async evaluate(
    ctx: ServiceContext,
    id: string,
    dto: EvaluateSupplierDto,
  ): Promise<SupplierConstruction | null> {
    const extension = await this.findById(ctx, id);
    if (!extension) {
      return null;
    }

    // Validate ratings (1.00 - 5.00)
    const ratings = [dto.qualityRating, dto.deliveryRating, dto.priceRating];
    for (const rating of ratings) {
      if (rating < 1 || rating > 5) {
        throw new Error('Ratings must be between 1.00 and 5.00');
      }
    }

    extension.qualityRating = dto.qualityRating;
    extension.deliveryRating = dto.deliveryRating;
    extension.priceRating = dto.priceRating;
    extension.lastEvaluationDate = new Date();
    if (ctx.userId) {
      extension.updatedById = ctx.userId;
    }

    return this.repository.save(extension);
  }

  async updateDocumentStatus(
    ctx: ServiceContext,
    id: string,
    hasValidDocuments: boolean,
    expiryDate?: Date,
  ): Promise<SupplierConstruction | null> {
    const extension = await this.findById(ctx, id);
    if (!extension) {
      return null;
    }

    extension.hasValidDocuments = hasValidDocuments;
    if (expiryDate) {
      extension.documentsExpiryDate = expiryDate;
    }
    if (ctx.userId) {
      extension.updatedById = ctx.userId;
    }

    return this.repository.save(extension);
  }

  async updateCreditTerms(
    ctx: ServiceContext,
    id: string,
    creditLimit: number,
    paymentDays: number,
  ): Promise<SupplierConstruction | null> {
    const extension = await this.findById(ctx, id);
    if (!extension) {
      return null;
    }

    if (creditLimit < 0) {
      throw new Error('Credit limit cannot be negative');
    }

    if (paymentDays < 0) {
      throw new Error('Payment days cannot be negative');
    }

    extension.creditLimit = creditLimit;
    extension.paymentDays = paymentDays;
    if (ctx.userId) {
      extension.updatedById = ctx.userId;
    }

    return this.repository.save(extension);
  }

  async findTopRated(ctx: ServiceContext, limit: number = 10): Promise<SupplierConstruction[]> {
    return this.repository
      .createQueryBuilder('sc')
      .where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sc.deleted_at IS NULL')
      .andWhere('sc.overall_rating IS NOT NULL')
      .orderBy('sc.overall_rating', 'DESC')
      .take(limit)
      .getMany();
  }

  async findBySpecialty(ctx: ServiceContext, specialty: string): Promise<SupplierConstruction[]> {
    return this.repository
      .createQueryBuilder('sc')
      .where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sc.deleted_at IS NULL')
      .andWhere(':specialty = ANY(sc.specialties)', { specialty })
      .orderBy('sc.overall_rating', 'DESC', 'NULLS LAST')
      .getMany();
  }

  async findMaterialsSuppliers(ctx: ServiceContext): Promise<SupplierConstruction[]> {
    return this.repository
      .createQueryBuilder('sc')
      .where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sc.deleted_at IS NULL')
      .andWhere('sc.is_materials_supplier = true')
      .orderBy('sc.overall_rating', 'DESC', 'NULLS LAST')
      .getMany();
  }

  async findServicesSuppliers(ctx: ServiceContext): Promise<SupplierConstruction[]> {
    return this.repository
      .createQueryBuilder('sc')
      .where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sc.deleted_at IS NULL')
      .andWhere('sc.is_services_supplier = true')
      .orderBy('sc.overall_rating', 'DESC', 'NULLS LAST')
      .getMany();
  }

  async findEquipmentSuppliers(ctx: ServiceContext): Promise<SupplierConstruction[]> {
    return this.repository
      .createQueryBuilder('sc')
      .where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sc.deleted_at IS NULL')
      .andWhere('sc.is_equipment_supplier = true')
      .orderBy('sc.overall_rating', 'DESC', 'NULLS LAST')
      .getMany();
  }

  async findWithExpiringDocuments(
    ctx: ServiceContext,
    daysAhead: number = 30,
  ): Promise<SupplierConstruction[]> {
    const expiryThreshold = new Date();
    expiryThreshold.setDate(expiryThreshold.getDate() + daysAhead);

    return this.repository
      .createQueryBuilder('sc')
      .where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sc.deleted_at IS NULL')
      .andWhere('sc.has_valid_documents = true')
      .andWhere('sc.documents_expiry_date IS NOT NULL')
      .andWhere('sc.documents_expiry_date <= :threshold', { threshold: expiryThreshold })
      .orderBy('sc.documents_expiry_date', 'ASC')
      .getMany();
  }

  async getSupplierStatistics(ctx: ServiceContext): Promise<{
    totalSuppliers: number;
    materialsSuppliers: number;
    servicesSuppliers: number;
    equipmentSuppliers: number;
    withValidDocs: number;
    withExpiringDocs: number;
    averageRating: number;
    evaluatedSuppliers: number;
  }> {
    const queryBuilder = this.repository
      .createQueryBuilder('sc')
      .where('sc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('sc.deleted_at IS NULL');

    const all = await queryBuilder.getMany();

    const expiryThreshold = new Date();
    expiryThreshold.setDate(expiryThreshold.getDate() + 30);

    const evaluated = all.filter((s) => s.overallRating !== null);
    const totalRating = evaluated.reduce((sum, s) => sum + Number(s.overallRating || 0), 0);

    return {
      totalSuppliers: all.length,
      materialsSuppliers: all.filter((s) => s.isMaterialsSupplier).length,
      servicesSuppliers: all.filter((s) => s.isServicesSupplier).length,
      equipmentSuppliers: all.filter((s) => s.isEquipmentSupplier).length,
      withValidDocs: all.filter((s) => s.hasValidDocuments).length,
      withExpiringDocs: all.filter(
        (s) => s.hasValidDocuments && s.documentsExpiryDate && s.documentsExpiryDate <= expiryThreshold,
      ).length,
      averageRating: evaluated.length > 0 ? totalRating / evaluated.length : 0,
      evaluatedSuppliers: evaluated.length,
    };
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.update(
      { id, tenantId: ctx.tenantId },
      { deletedAt: new Date(), deletedById: ctx.userId },
    );
    return (result.affected ?? 0) > 0;
  }
}
