/**
 * TicketAssignmentService - Servicio de asignaciones de tickets
 *
 * Gestiona la asignación y reasignación de tickets a técnicos.
 *
 * @module Quality (MAI-009)
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { TicketAssignment, AssignmentStatus } from '../entities/ticket-assignment.entity';
import { PostSaleTicket } from '../entities/post-sale-ticket.entity';

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

export interface CreateAssignmentDto {
  technicianId: string;
  scheduledDate?: Date;
  scheduledTime?: string;
}

export interface UpdateAssignmentDto {
  scheduledDate?: Date;
  scheduledTime?: string;
  workNotes?: string;
}

export interface ReassignDto {
  newTechnicianId: string;
  reassignmentReason: string;
  scheduledDate?: Date;
  scheduledTime?: string;
}

export interface AssignmentFilters {
  ticketId?: string;
  technicianId?: string;
  status?: AssignmentStatus;
  isCurrent?: boolean;
  scheduledDateFrom?: Date;
  scheduledDateTo?: Date;
}

export class TicketAssignmentService {
  constructor(
    private readonly assignmentRepository: Repository<TicketAssignment>,
    private readonly ticketRepository: Repository<PostSaleTicket>
  ) {}

  async findByTicket(ctx: ServiceContext, ticketId: string): Promise<TicketAssignment[]> {
    return this.assignmentRepository.find({
      where: {
        tenantId: ctx.tenantId,
        ticketId,
      } as FindOptionsWhere<TicketAssignment>,
      relations: ['technician', 'assignedBy'],
      order: { assignedAt: 'DESC' },
    });
  }

  async findCurrentByTicket(ctx: ServiceContext, ticketId: string): Promise<TicketAssignment | null> {
    return this.assignmentRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        ticketId,
        isCurrent: true,
      } as FindOptionsWhere<TicketAssignment>,
      relations: ['technician', 'assignedBy'],
    });
  }

  async findById(ctx: ServiceContext, id: string): Promise<TicketAssignment | null> {
    return this.assignmentRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<TicketAssignment>,
      relations: ['technician', 'assignedBy', 'ticket'],
    });
  }

  async findByTechnician(
    ctx: ServiceContext,
    technicianId: string,
    filters: { status?: AssignmentStatus; isCurrent?: boolean } = {}
  ): Promise<TicketAssignment[]> {
    const where: FindOptionsWhere<TicketAssignment> = {
      tenantId: ctx.tenantId,
      technicianId,
    };

    if (filters.status) {
      where.status = filters.status;
    }

    if (filters.isCurrent !== undefined) {
      where.isCurrent = filters.isCurrent;
    }

    return this.assignmentRepository.find({
      where,
      relations: ['ticket'],
      order: { scheduledDate: 'ASC', assignedAt: 'DESC' },
    });
  }

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

    const queryBuilder = this.assignmentRepository
      .createQueryBuilder('ta')
      .leftJoinAndSelect('ta.ticket', 'ticket')
      .leftJoinAndSelect('ta.technician', 'technician')
      .leftJoinAndSelect('ta.assignedBy', 'assignedBy')
      .where('ta.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.ticketId) {
      queryBuilder.andWhere('ta.ticket_id = :ticketId', { ticketId: filters.ticketId });
    }

    if (filters.technicianId) {
      queryBuilder.andWhere('ta.technician_id = :technicianId', { technicianId: filters.technicianId });
    }

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

    if (filters.isCurrent !== undefined) {
      queryBuilder.andWhere('ta.is_current = :isCurrent', { isCurrent: filters.isCurrent });
    }

    if (filters.scheduledDateFrom) {
      queryBuilder.andWhere('ta.scheduled_date >= :scheduledDateFrom', {
        scheduledDateFrom: filters.scheduledDateFrom,
      });
    }

    if (filters.scheduledDateTo) {
      queryBuilder.andWhere('ta.scheduled_date <= :scheduledDateTo', {
        scheduledDateTo: filters.scheduledDateTo,
      });
    }

    queryBuilder
      .orderBy('ta.scheduled_date', 'ASC')
      .addOrderBy('ta.assigned_at', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  async assignTicket(
    ctx: ServiceContext,
    ticketId: string,
    dto: CreateAssignmentDto
  ): Promise<TicketAssignment> {
    // Validate ticket exists
    const ticket = await this.ticketRepository.findOne({
      where: {
        id: ticketId,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<PostSaleTicket>,
    });

    if (!ticket) {
      throw new Error('Ticket not found');
    }

    if (ticket.status === 'closed' || ticket.status === 'cancelled') {
      throw new Error('Cannot assign closed or cancelled tickets');
    }

    // Mark previous current assignment as not current
    const currentAssignment = await this.findCurrentByTicket(ctx, ticketId);
    if (currentAssignment) {
      currentAssignment.isCurrent = false;
      currentAssignment.status = 'reassigned';
      await this.assignmentRepository.save(currentAssignment);
    }

    // Create new assignment
    const assignment = this.assignmentRepository.create({
      tenantId: ctx.tenantId,
      ticketId,
      technicianId: dto.technicianId,
      assignedAt: new Date(),
      assignedById: ctx.userId || '',
      scheduledDate: dto.scheduledDate,
      scheduledTime: dto.scheduledTime,
      status: 'assigned',
      isCurrent: true,
    });

    const savedAssignment = await this.assignmentRepository.save(assignment);

    // Update ticket status
    ticket.status = 'assigned';
    ticket.assignedAt = new Date();
    ticket.updatedById = ctx.userId || '';
    await this.ticketRepository.save(ticket);

    return savedAssignment;
  }

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

    if (!assignment.isCurrent) {
      throw new Error('Can only accept current assignment');
    }

    if (assignment.status !== 'assigned') {
      throw new Error('Can only accept assigned assignments');
    }

    assignment.status = 'accepted';
    assignment.acceptedAt = new Date();

    return this.assignmentRepository.save(assignment);
  }

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

    if (!assignment.isCurrent) {
      throw new Error('Can only start work on current assignment');
    }

    if (assignment.status !== 'assigned' && assignment.status !== 'accepted') {
      throw new Error('Can only start work on assigned or accepted assignments');
    }

    assignment.status = 'in_progress';
    if (!assignment.acceptedAt) {
      assignment.acceptedAt = new Date();
    }

    const savedAssignment = await this.assignmentRepository.save(assignment);

    // Update ticket status
    const ticket = await this.ticketRepository.findOne({
      where: {
        id: assignment.ticketId,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<PostSaleTicket>,
    });

    if (ticket) {
      ticket.status = 'in_progress';
      ticket.updatedById = ctx.userId || '';
      await this.ticketRepository.save(ticket);
    }

    return savedAssignment;
  }

  async completeAssignment(ctx: ServiceContext, id: string, workNotes: string): Promise<TicketAssignment | null> {
    const assignment = await this.findById(ctx, id);
    if (!assignment) {
      return null;
    }

    if (!assignment.isCurrent) {
      throw new Error('Can only complete current assignment');
    }

    if (assignment.status !== 'in_progress') {
      throw new Error('Can only complete in-progress assignments');
    }

    assignment.status = 'completed';
    assignment.completedAt = new Date();
    assignment.workNotes = workNotes;

    return this.assignmentRepository.save(assignment);
  }

  async reassign(ctx: ServiceContext, ticketId: string, dto: ReassignDto): Promise<TicketAssignment> {
    // Get current assignment
    const currentAssignment = await this.findCurrentByTicket(ctx, ticketId);
    if (currentAssignment) {
      currentAssignment.isCurrent = false;
      currentAssignment.status = 'reassigned';
      currentAssignment.reassignmentReason = dto.reassignmentReason;
      await this.assignmentRepository.save(currentAssignment);
    }

    // Create new assignment
    const newAssignment = this.assignmentRepository.create({
      tenantId: ctx.tenantId,
      ticketId,
      technicianId: dto.newTechnicianId,
      assignedAt: new Date(),
      assignedById: ctx.userId || '',
      scheduledDate: dto.scheduledDate,
      scheduledTime: dto.scheduledTime,
      status: 'assigned',
      isCurrent: true,
    });

    const savedAssignment = await this.assignmentRepository.save(newAssignment);

    // Update ticket status back to assigned
    const ticket = await this.ticketRepository.findOne({
      where: {
        id: ticketId,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<PostSaleTicket>,
    });

    if (ticket && ticket.status === 'in_progress') {
      ticket.status = 'assigned';
      ticket.updatedById = ctx.userId || '';
      await this.ticketRepository.save(ticket);
    }

    return savedAssignment;
  }

  async updateSchedule(
    ctx: ServiceContext,
    id: string,
    dto: UpdateAssignmentDto
  ): Promise<TicketAssignment | null> {
    const assignment = await this.findById(ctx, id);
    if (!assignment) {
      return null;
    }

    if (!assignment.isCurrent) {
      throw new Error('Can only update current assignment');
    }

    if (assignment.status === 'completed' || assignment.status === 'reassigned') {
      throw new Error('Cannot update completed or reassigned assignments');
    }

    if (dto.scheduledDate !== undefined) {
      assignment.scheduledDate = dto.scheduledDate;
    }
    if (dto.scheduledTime !== undefined) {
      assignment.scheduledTime = dto.scheduledTime;
    }
    if (dto.workNotes !== undefined) {
      assignment.workNotes = dto.workNotes;
    }

    return this.assignmentRepository.save(assignment);
  }

  async getTechnicianSchedule(
    ctx: ServiceContext,
    technicianId: string,
    dateFrom: Date,
    dateTo: Date
  ): Promise<TicketAssignment[]> {
    return this.assignmentRepository
      .createQueryBuilder('ta')
      .leftJoinAndSelect('ta.ticket', 'ticket')
      .where('ta.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('ta.technician_id = :technicianId', { technicianId })
      .andWhere('ta.is_current = true')
      .andWhere('ta.status IN (:...statuses)', { statuses: ['assigned', 'accepted', 'in_progress'] })
      .andWhere('ta.scheduled_date >= :dateFrom', { dateFrom })
      .andWhere('ta.scheduled_date <= :dateTo', { dateTo })
      .orderBy('ta.scheduled_date', 'ASC')
      .addOrderBy('ta.scheduled_time', 'ASC')
      .getMany();
  }

  async getTechnicianWorkload(ctx: ServiceContext, technicianId: string): Promise<{
    assigned: number;
    accepted: number;
    inProgress: number;
    completedToday: number;
    completedThisWeek: number;
    averageCompletionTime: number;
  }> {
    const assignments = await this.assignmentRepository.find({
      where: {
        tenantId: ctx.tenantId,
        technicianId,
      } as FindOptionsWhere<TicketAssignment>,
    });

    const now = new Date();
    const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const startOfWeek = new Date(startOfDay);
    startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay());

    const currentAssignments = assignments.filter(a => a.isCurrent);
    const assigned = currentAssignments.filter(a => a.status === 'assigned').length;
    const accepted = currentAssignments.filter(a => a.status === 'accepted').length;
    const inProgress = currentAssignments.filter(a => a.status === 'in_progress').length;

    const completedToday = assignments.filter(
      a => a.status === 'completed' && a.completedAt && a.completedAt >= startOfDay
    ).length;

    const completedThisWeek = assignments.filter(
      a => a.status === 'completed' && a.completedAt && a.completedAt >= startOfWeek
    ).length;

    // Calculate average completion time (in hours)
    const completedWithTime = assignments.filter(
      a => a.status === 'completed' && a.completedAt && a.acceptedAt
    );

    let averageCompletionTime = 0;
    if (completedWithTime.length > 0) {
      const totalHours = completedWithTime.reduce((sum, a) => {
        const accepted = new Date(a.acceptedAt).getTime();
        const completed = new Date(a.completedAt).getTime();
        return sum + (completed - accepted) / (1000 * 60 * 60);
      }, 0);
      averageCompletionTime = Math.round((totalHours / completedWithTime.length) * 100) / 100;
    }

    return {
      assigned,
      accepted,
      inProgress,
      completedToday,
      completedThisWeek,
      averageCompletionTime,
    };
  }

  async getAssignmentHistory(ctx: ServiceContext, ticketId: string): Promise<{
    assignment: TicketAssignment;
    duration?: number;
  }[]> {
    const assignments = await this.findByTicket(ctx, ticketId);

    return assignments.map((assignment, index) => {
      let duration: number | undefined;

      if (assignment.completedAt && assignment.acceptedAt) {
        // Time from acceptance to completion
        duration = Math.round(
          (assignment.completedAt.getTime() - assignment.acceptedAt.getTime()) / (1000 * 60 * 60)
        );
      } else if (assignment.status === 'reassigned' && assignments[index + 1]) {
        // Time until reassignment
        const nextAssignment = assignments[index + 1];
        const startTime = assignment.acceptedAt || assignment.assignedAt;
        duration = Math.round(
          (nextAssignment.assignedAt.getTime() - startTime.getTime()) / (1000 * 60 * 60)
        );
      }

      return { assignment, duration };
    });
  }
}
