/**
 * TicketService - Servicio de tickets postventa
 *
 * Gestión de tickets de garantía con SLA.
 *
 * @module Quality
 */

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

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

export interface CreateTicketDto {
  loteId: string;
  derechohabienteId?: string;
  category: TicketCategory;
  title: string;
  description: string;
  photoUrl?: string;
  contactName?: string;
  contactPhone?: string;
}

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

export interface ResolveTicketDto {
  resolutionNotes: string;
  resolutionPhotoUrl?: string;
}

export interface TicketFilters {
  loteId?: string;
  derechohabienteId?: string;
  category?: TicketCategory;
  priority?: TicketPriority;
  status?: TicketStatus;
  slaBreached?: boolean;
  assignedTo?: string;
  dateFrom?: Date;
  dateTo?: Date;
}

// SLA hours by priority
const SLA_HOURS: Record<TicketPriority, number> = {
  urgent: 24,
  high: 48,
  medium: 168, // 7 days
  low: 360, // 15 days
};

// Auto-priority by category
const CATEGORY_PRIORITY: Record<TicketCategory, TicketPriority> = {
  plumbing: 'high',
  electrical: 'high',
  structural: 'urgent',
  finishes: 'medium',
  carpentry: 'medium',
  other: 'low',
};

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

  private generateTicketNumber(): 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, 8).toUpperCase();
    return `TKT-${year}${month}-${random}`;
  }

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

    const queryBuilder = this.ticketRepository
      .createQueryBuilder('tkt')
      .leftJoinAndSelect('tkt.assignments', 'assignments', 'assignments.is_current = true')
      .leftJoinAndSelect('assignments.technician', 'technician')
      .where('tkt.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (filters.derechohabienteId) {
      queryBuilder.andWhere('tkt.derechohabiente_id = :derechohabienteId', {
        derechohabienteId: filters.derechohabienteId,
      });
    }

    if (filters.category) {
      queryBuilder.andWhere('tkt.category = :category', { category: filters.category });
    }

    if (filters.priority) {
      queryBuilder.andWhere('tkt.priority = :priority', { priority: filters.priority });
    }

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

    if (filters.slaBreached !== undefined) {
      queryBuilder.andWhere('tkt.sla_breached = :slaBreached', { slaBreached: filters.slaBreached });
    }

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

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

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

    queryBuilder
      .orderBy('tkt.priority', 'ASC')
      .addOrderBy('tkt.sla_due_at', 'ASC')
      .skip(skip)
      .take(limit);

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

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

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

  async findWithDetails(ctx: ServiceContext, id: string): Promise<PostSaleTicket | null> {
    return this.ticketRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      } as unknown as FindOptionsWhere<PostSaleTicket>,
      relations: ['assignments', 'assignments.technician', 'assignments.assignedBy', 'createdBy'],
    });
  }

  async create(ctx: ServiceContext, dto: CreateTicketDto): Promise<PostSaleTicket> {
    // Determine priority based on category
    const priority = CATEGORY_PRIORITY[dto.category];
    const slaHours = SLA_HOURS[priority];
    const slaDueAt = new Date();
    slaDueAt.setHours(slaDueAt.getHours() + slaHours);

    const ticket = this.ticketRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      loteId: dto.loteId,
      derechohabienteId: dto.derechohabienteId,
      ticketNumber: this.generateTicketNumber(),
      category: dto.category,
      priority,
      title: dto.title,
      description: dto.description,
      photoUrl: dto.photoUrl,
      contactName: dto.contactName,
      contactPhone: dto.contactPhone,
      status: 'created',
      slaHours,
      slaDueAt,
    });

    return this.ticketRepository.save(ticket);
  }

  async assign(ctx: ServiceContext, id: string, dto: AssignTicketDto): Promise<PostSaleTicket | null> {
    const ticket = await this.findWithDetails(ctx, id);
    if (!ticket) {
      return null;
    }

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

    // Mark previous assignments as not current
    if (ticket.assignments && ticket.assignments.length > 0) {
      for (const assignment of ticket.assignments) {
        if (assignment.isCurrent) {
          assignment.isCurrent = false;
          assignment.status = 'reassigned';
          await this.assignmentRepository.save(assignment);
        }
      }
    }

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

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

    return this.ticketRepository.save(ticket);
  }

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

    if (ticket.status !== 'assigned') {
      throw new Error('Can only start work on assigned tickets');
    }

    // Update current assignment
    const currentAssignment = ticket.assignments?.find(a => a.isCurrent);
    if (currentAssignment) {
      currentAssignment.status = 'in_progress';
      currentAssignment.acceptedAt = new Date();
      await this.assignmentRepository.save(currentAssignment);
    }

    ticket.status = 'in_progress';
    ticket.updatedById = ctx.userId || '';

    return this.ticketRepository.save(ticket);
  }

  async resolve(ctx: ServiceContext, id: string, dto: ResolveTicketDto): Promise<PostSaleTicket | null> {
    const ticket = await this.findWithDetails(ctx, id);
    if (!ticket) {
      return null;
    }

    if (ticket.status !== 'in_progress') {
      throw new Error('Can only resolve in-progress tickets');
    }

    // Update current assignment
    const currentAssignment = ticket.assignments?.find(a => a.isCurrent);
    if (currentAssignment) {
      currentAssignment.status = 'completed';
      currentAssignment.completedAt = new Date();
      currentAssignment.workNotes = dto.resolutionNotes;
      await this.assignmentRepository.save(currentAssignment);
    }

    ticket.status = 'resolved';
    ticket.resolvedAt = new Date();
    ticket.resolutionNotes = dto.resolutionNotes;
    if (dto.resolutionPhotoUrl) {
      ticket.resolutionPhotoUrl = dto.resolutionPhotoUrl;
    }
    ticket.updatedById = ctx.userId || '';

    // Check if SLA was breached
    if (new Date() > ticket.slaDueAt) {
      ticket.slaBreached = true;
    }

    return this.ticketRepository.save(ticket);
  }

  async close(ctx: ServiceContext, id: string, rating?: number, comment?: string): Promise<PostSaleTicket | null> {
    const ticket = await this.findById(ctx, id);
    if (!ticket) {
      return null;
    }

    if (ticket.status !== 'resolved') {
      throw new Error('Can only close resolved tickets');
    }

    ticket.status = 'closed';
    ticket.closedAt = new Date();
    if (rating !== undefined) {
      ticket.satisfactionRating = rating;
    }
    if (comment) {
      ticket.satisfactionComment = comment;
    }
    ticket.updatedById = ctx.userId || '';

    return this.ticketRepository.save(ticket);
  }

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

    if (ticket.status === 'closed') {
      throw new Error('Cannot cancel closed tickets');
    }

    ticket.status = 'cancelled';
    ticket.resolutionNotes = `[CANCELLED] ${reason}`;
    ticket.updatedById = ctx.userId || '';

    return this.ticketRepository.save(ticket);
  }

  async checkSlaBreaches(ctx: ServiceContext): Promise<number> {
    const result = await this.ticketRepository.update(
      {
        tenantId: ctx.tenantId,
        slaBreached: false,
        slaDueAt: LessThan(new Date()),
        status: 'created' as TicketStatus,
      } as FindOptionsWhere<PostSaleTicket>,
      { slaBreached: true }
    );

    // Also check assigned and in_progress
    await this.ticketRepository.update(
      {
        tenantId: ctx.tenantId,
        slaBreached: false,
        slaDueAt: LessThan(new Date()),
        status: 'assigned' as TicketStatus,
      } as FindOptionsWhere<PostSaleTicket>,
      { slaBreached: true }
    );

    await this.ticketRepository.update(
      {
        tenantId: ctx.tenantId,
        slaBreached: false,
        slaDueAt: LessThan(new Date()),
        status: 'in_progress' as TicketStatus,
      } as FindOptionsWhere<PostSaleTicket>,
      { slaBreached: true }
    );

    return result.affected || 0;
  }

  async getSlaStats(ctx: ServiceContext): Promise<{ total: number; breached: number; complianceRate: number }> {
    const total = await this.ticketRepository.count({
      where: {
        tenantId: ctx.tenantId,
        status: 'closed' as TicketStatus,
      } as FindOptionsWhere<PostSaleTicket>,
    });

    const breached = await this.ticketRepository.count({
      where: {
        tenantId: ctx.tenantId,
        status: 'closed' as TicketStatus,
        slaBreached: true,
      } as FindOptionsWhere<PostSaleTicket>,
    });

    const complianceRate = total > 0 ? ((total - breached) / total) * 100 : 100;

    return { total, breached, complianceRate };
  }
}
