/**
 * NonConformityService - Servicio de no conformidades
 *
 * Gestiona el ciclo de vida de no conformidades detectadas en inspecciones.
 *
 * @module Quality (MAI-009)
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { NonConformity, NCSeverity, NCStatus } from '../entities/non-conformity.entity';
import { CorrectiveAction } from '../entities/corrective-action.entity';

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

export interface CreateNonConformityDto {
  inspectionId?: string;
  loteId: string;
  detectionDate: Date;
  category: string;
  severity: NCSeverity;
  description: string;
  rootCause?: string;
  photoUrl?: string;
  contractorId?: string;
  dueDate?: Date;
}

export interface UpdateNonConformityDto {
  loteId?: string;
  category?: string;
  severity?: NCSeverity;
  description?: string;
  rootCause?: string;
  photoUrl?: string;
  contractorId?: string;
  dueDate?: Date;
}

export interface CloseNonConformityDto {
  closurePhotoUrl?: string;
  closureNotes?: string;
}

export interface VerifyNonConformityDto {
  verificationNotes?: string;
}

export interface NonConformityFilters {
  inspectionId?: string;
  loteId?: string;
  contractorId?: string;
  severity?: NCSeverity;
  status?: NCStatus;
  category?: string;
  detectionDateFrom?: Date;
  detectionDateTo?: Date;
  dueDateFrom?: Date;
  dueDateTo?: Date;
  search?: string;
}

// SLA days by severity
const SLA_DAYS: Record<NCSeverity, number> = {
  critical: 3,
  major: 7,
  minor: 15,
};

export class NonConformityService {
  constructor(
    private readonly ncRepository: Repository<NonConformity>,
    private readonly actionRepository: Repository<CorrectiveAction>
  ) {}

  private generateNCNumber(): string {
    const now = new Date();
    const year = now.getFullYear().toString().slice(-2);
    const month = (now.getMonth() + 1).toString().padStart(2, '0');
    const random = Math.random().toString(36).substring(2, 6).toUpperCase();
    return `NC-${year}${month}-${random}`;
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: NonConformityFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<{ data: NonConformity[]; total: number; page: number; limit: number }> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.ncRepository
      .createQueryBuilder('nc')
      .leftJoinAndSelect('nc.inspection', 'inspection')
      .leftJoinAndSelect('nc.createdBy', 'createdBy')
      .leftJoinAndSelect('nc.closedBy', 'closedBy')
      .leftJoinAndSelect('nc.verifiedBy', 'verifiedBy')
      .where('nc.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.inspectionId) {
      queryBuilder.andWhere('nc.inspection_id = :inspectionId', { inspectionId: filters.inspectionId });
    }

    if (filters.loteId) {
      queryBuilder.andWhere('nc.lote_id = :loteId', { loteId: filters.loteId });
    }

    if (filters.contractorId) {
      queryBuilder.andWhere('nc.contractor_id = :contractorId', { contractorId: filters.contractorId });
    }

    if (filters.severity) {
      queryBuilder.andWhere('nc.severity = :severity', { severity: filters.severity });
    }

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

    if (filters.category) {
      queryBuilder.andWhere('nc.category ILIKE :category', { category: `%${filters.category}%` });
    }

    if (filters.detectionDateFrom) {
      queryBuilder.andWhere('nc.detection_date >= :detectionDateFrom', {
        detectionDateFrom: filters.detectionDateFrom,
      });
    }

    if (filters.detectionDateTo) {
      queryBuilder.andWhere('nc.detection_date <= :detectionDateTo', {
        detectionDateTo: filters.detectionDateTo,
      });
    }

    if (filters.dueDateFrom) {
      queryBuilder.andWhere('nc.due_date >= :dueDateFrom', { dueDateFrom: filters.dueDateFrom });
    }

    if (filters.dueDateTo) {
      queryBuilder.andWhere('nc.due_date <= :dueDateTo', { dueDateTo: filters.dueDateTo });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(nc.nc_number ILIKE :search OR nc.description ILIKE :search OR nc.category ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    queryBuilder
      .orderBy('nc.severity', 'ASC')
      .addOrderBy('nc.detection_date', 'DESC')
      .skip(skip)
      .take(limit);

    const [data, total] = await queryBuilder.getManyAndCount();

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

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

  async findWithDetails(ctx: ServiceContext, id: string): Promise<NonConformity | null> {
    return this.ncRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<NonConformity>,
      relations: ['inspection', 'correctiveActions', 'correctiveActions.responsible', 'createdBy', 'closedBy', 'verifiedBy'],
    });
  }

  async findByLote(ctx: ServiceContext, loteId: string): Promise<NonConformity[]> {
    return this.ncRepository.find({
      where: {
        tenantId: ctx.tenantId,
        loteId,
      } as FindOptionsWhere<NonConformity>,
      relations: ['correctiveActions'],
      order: { detectionDate: 'DESC' },
    });
  }

  async findByInspection(ctx: ServiceContext, inspectionId: string): Promise<NonConformity[]> {
    return this.ncRepository.find({
      where: {
        tenantId: ctx.tenantId,
        inspectionId,
      } as FindOptionsWhere<NonConformity>,
      relations: ['correctiveActions'],
      order: { severity: 'ASC' },
    });
  }

  async findByNumber(ctx: ServiceContext, ncNumber: string): Promise<NonConformity | null> {
    return this.ncRepository.findOne({
      where: {
        ncNumber,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<NonConformity>,
      relations: ['correctiveActions', 'createdBy'],
    });
  }

  async create(ctx: ServiceContext, dto: CreateNonConformityDto): Promise<NonConformity> {
    // Calculate due date based on severity if not provided
    let dueDate = dto.dueDate;
    if (!dueDate) {
      dueDate = new Date();
      dueDate.setDate(dueDate.getDate() + SLA_DAYS[dto.severity]);
    }

    const nc = this.ncRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      inspectionId: dto.inspectionId,
      loteId: dto.loteId,
      ncNumber: this.generateNCNumber(),
      detectionDate: dto.detectionDate,
      category: dto.category,
      severity: dto.severity,
      description: dto.description,
      rootCause: dto.rootCause,
      photoUrl: dto.photoUrl,
      contractorId: dto.contractorId,
      dueDate,
      status: 'open',
    });

    return this.ncRepository.save(nc);
  }

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

    if (nc.status === 'closed' || nc.status === 'verified') {
      throw new Error('Cannot update closed or verified non-conformities');
    }

    // If severity changes, recalculate due date
    if (dto.severity && dto.severity !== nc.severity && !dto.dueDate) {
      const newDueDate = new Date(nc.detectionDate);
      newDueDate.setDate(newDueDate.getDate() + SLA_DAYS[dto.severity]);
      dto.dueDate = newDueDate;
    }

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

    return this.ncRepository.save(nc);
  }

  async assignContractor(ctx: ServiceContext, id: string, contractorId: string): Promise<NonConformity | null> {
    const nc = await this.findById(ctx, id);
    if (!nc) {
      return null;
    }

    if (nc.status === 'closed' || nc.status === 'verified') {
      throw new Error('Cannot assign contractor to closed or verified non-conformities');
    }

    nc.contractorId = contractorId;
    nc.updatedById = ctx.userId || '';

    // Move to in_progress if open
    if (nc.status === 'open') {
      nc.status = 'in_progress';
    }

    return this.ncRepository.save(nc);
  }

  async setRootCause(ctx: ServiceContext, id: string, rootCause: string): Promise<NonConformity | null> {
    const nc = await this.findById(ctx, id);
    if (!nc) {
      return null;
    }

    if (nc.status === 'verified') {
      throw new Error('Cannot modify verified non-conformities');
    }

    nc.rootCause = rootCause;
    nc.updatedById = ctx.userId || '';

    return this.ncRepository.save(nc);
  }

  async close(ctx: ServiceContext, id: string, dto: CloseNonConformityDto): Promise<NonConformity | null> {
    const nc = await this.findWithDetails(ctx, id);
    if (!nc) {
      return null;
    }

    if (nc.status !== 'in_progress') {
      throw new Error('Can only close in-progress non-conformities');
    }

    // Check that all corrective actions are completed
    const pendingActions = nc.correctiveActions?.filter(
      a => a.status === 'pending' || a.status === 'in_progress'
    );

    if (pendingActions && pendingActions.length > 0) {
      throw new Error('Cannot close non-conformity with pending corrective actions');
    }

    nc.status = 'closed';
    nc.closedAt = new Date();
    nc.closedById = ctx.userId || '';
    nc.closurePhotoUrl = dto.closurePhotoUrl || nc.closurePhotoUrl;
    nc.closureNotes = dto.closureNotes || nc.closureNotes;
    nc.updatedById = ctx.userId || '';

    return this.ncRepository.save(nc);
  }

  async verify(ctx: ServiceContext, id: string, dto: VerifyNonConformityDto): Promise<NonConformity | null> {
    const nc = await this.findWithDetails(ctx, id);
    if (!nc) {
      return null;
    }

    if (nc.status !== 'closed') {
      throw new Error('Can only verify closed non-conformities');
    }

    // Check that all corrective actions are verified
    const unverifiedActions = nc.correctiveActions?.filter(a => a.status !== 'verified');

    if (unverifiedActions && unverifiedActions.length > 0) {
      throw new Error('Cannot verify non-conformity with unverified corrective actions');
    }

    nc.status = 'verified';
    nc.verifiedAt = new Date();
    nc.verifiedById = ctx.userId || '';
    if (dto.verificationNotes) {
      nc.closureNotes = `${nc.closureNotes || ''}\n\n[VERIFICATION]: ${dto.verificationNotes}`;
    }
    nc.updatedById = ctx.userId || '';

    return this.ncRepository.save(nc);
  }

  async reopen(ctx: ServiceContext, id: string, reason: string): Promise<NonConformity | null> {
    const nc = await this.findById(ctx, id);
    if (!nc) {
      return null;
    }

    if (nc.status !== 'closed' && nc.status !== 'verified') {
      throw new Error('Can only reopen closed or verified non-conformities');
    }

    nc.status = 'in_progress';
    nc.closedAt = null as unknown as Date;
    nc.closedById = null as unknown as string;
    nc.verifiedAt = null as unknown as Date;
    nc.verifiedById = null as unknown as string;
    nc.closureNotes = `${nc.closureNotes || ''}\n\n[REOPENED]: ${reason}`;
    nc.updatedById = ctx.userId || '';

    // Extend due date
    const newDueDate = new Date();
    newDueDate.setDate(newDueDate.getDate() + SLA_DAYS[nc.severity]);
    nc.dueDate = newDueDate;

    return this.ncRepository.save(nc);
  }

  async getOpenBySeverity(ctx: ServiceContext): Promise<{ severity: NCSeverity; count: number }[]> {
    const result = await this.ncRepository
      .createQueryBuilder('nc')
      .select('nc.severity', 'severity')
      .addSelect('COUNT(*)', 'count')
      .where('nc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('nc.status IN (:...statuses)', { statuses: ['open', 'in_progress'] })
      .groupBy('nc.severity')
      .getRawMany();

    return result.map(r => ({
      severity: r.severity as NCSeverity,
      count: parseInt(r.count, 10),
    }));
  }

  async getOverdueNCs(ctx: ServiceContext): Promise<NonConformity[]> {
    return this.ncRepository
      .createQueryBuilder('nc')
      .leftJoinAndSelect('nc.correctiveActions', 'ca')
      .where('nc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('nc.status IN (:...statuses)', { statuses: ['open', 'in_progress'] })
      .andWhere('nc.due_date < :now', { now: new Date() })
      .orderBy('nc.severity', 'ASC')
      .addOrderBy('nc.due_date', 'ASC')
      .getMany();
  }

  async getStatsByContractor(ctx: ServiceContext, contractorId: string): Promise<{
    total: number;
    open: number;
    inProgress: number;
    closed: number;
    verified: number;
    bySeverity: Record<NCSeverity, number>;
    avgResolutionDays: number;
  }> {
    const ncs = await this.ncRepository.find({
      where: {
        tenantId: ctx.tenantId,
        contractorId,
      } as FindOptionsWhere<NonConformity>,
    });

    const total = ncs.length;
    const open = ncs.filter(n => n.status === 'open').length;
    const inProgress = ncs.filter(n => n.status === 'in_progress').length;
    const closed = ncs.filter(n => n.status === 'closed').length;
    const verified = ncs.filter(n => n.status === 'verified').length;

    const bySeverity: Record<NCSeverity, number> = {
      minor: ncs.filter(n => n.severity === 'minor').length,
      major: ncs.filter(n => n.severity === 'major').length,
      critical: ncs.filter(n => n.severity === 'critical').length,
    };

    // Calculate average resolution time for closed/verified NCs
    const resolvedNCs = ncs.filter(n => n.closedAt);
    let avgResolutionDays = 0;
    if (resolvedNCs.length > 0) {
      const totalDays = resolvedNCs.reduce((sum, nc) => {
        const detection = new Date(nc.detectionDate);
        const closure = new Date(nc.closedAt);
        const days = Math.ceil((closure.getTime() - detection.getTime()) / (1000 * 60 * 60 * 24));
        return sum + days;
      }, 0);
      avgResolutionDays = Math.round(totalDays / resolvedNCs.length);
    }

    return { total, open, inProgress, closed, verified, bySeverity, avgResolutionDays };
  }

  async getStatistics(ctx: ServiceContext): Promise<{
    total: number;
    byStatus: Record<NCStatus, number>;
    bySeverity: Record<NCSeverity, number>;
    overdue: number;
    avgResolutionDays: number;
    totalCorrectiveActions: number;
  }> {
    const ncs = await this.ncRepository.find({
      where: { tenantId: ctx.tenantId } as FindOptionsWhere<NonConformity>,
    });

    const actions = await this.actionRepository.find({
      where: { tenantId: ctx.tenantId } as FindOptionsWhere<CorrectiveAction>,
    });

    const now = new Date();
    const total = ncs.length;

    const byStatus: Record<NCStatus, number> = {
      open: ncs.filter(n => n.status === 'open').length,
      in_progress: ncs.filter(n => n.status === 'in_progress').length,
      closed: ncs.filter(n => n.status === 'closed').length,
      verified: ncs.filter(n => n.status === 'verified').length,
    };

    const bySeverity: Record<NCSeverity, number> = {
      minor: ncs.filter(n => n.severity === 'minor').length,
      major: ncs.filter(n => n.severity === 'major').length,
      critical: ncs.filter(n => n.severity === 'critical').length,
    };

    const overdue = ncs.filter(
      n => (n.status === 'open' || n.status === 'in_progress') && n.dueDate && n.dueDate < now
    ).length;

    // Calculate average resolution time
    const resolvedNCs = ncs.filter(n => n.closedAt);
    let avgResolutionDays = 0;
    if (resolvedNCs.length > 0) {
      const totalDays = resolvedNCs.reduce((sum, nc) => {
        const detection = new Date(nc.detectionDate);
        const closure = new Date(nc.closedAt);
        const days = Math.ceil((closure.getTime() - detection.getTime()) / (1000 * 60 * 60 * 24));
        return sum + days;
      }, 0);
      avgResolutionDays = Math.round(totalDays / resolvedNCs.length);
    }

    return {
      total,
      byStatus,
      bySeverity,
      overdue,
      avgResolutionDays,
      totalCorrectiveActions: actions.length,
    };
  }
}
