/**
 * WorkOrder Service
 * ERP Construccion - Modulo Activos (MAE-015)
 *
 * Logica de negocio para ordenes de trabajo de mantenimiento.
 */

import { Repository, DataSource, LessThanOrEqual, In, IsNull } from 'typeorm';
import { WorkOrder, WorkOrderStatus, WorkOrderPriority, MaintenanceType, WorkOrderStatusValues, WorkOrderPriorityValues } from '../entities/work-order.entity';
import { WorkOrderPart } from '../entities/work-order-part.entity';
import { MaintenanceHistory } from '../entities/maintenance-history.entity';
import { MaintenancePlan } from '../entities/maintenance-plan.entity';
import { Asset } from '../entities/asset.entity';

// DTOs
export interface CreateWorkOrderDto {
  assetId: string;
  maintenanceType: MaintenanceType;
  priority?: WorkOrderPriority;
  title: string;
  description?: string;
  problemReported?: string;
  projectId?: string;
  projectName?: string;
  scheduledStartDate?: Date;
  scheduledEndDate?: Date;
  assignedToId?: string;
  assignedToName?: string;
  estimatedHours?: number;
  activitiesChecklist?: Record<string, any>[];
  isScheduled?: boolean;
  scheduleId?: string;
  planId?: string;
}

export interface UpdateWorkOrderDto {
  status?: WorkOrderStatus;
  priority?: WorkOrderPriority;
  title?: string;
  description?: string;
  diagnosis?: string;
  scheduledStartDate?: Date;
  scheduledEndDate?: Date;
  actualStartDate?: Date;
  actualEndDate?: Date;
  assignedToId?: string;
  assignedToName?: string;
  workPerformed?: string;
  findings?: string;
  recommendations?: string;
  activitiesChecklist?: Record<string, any>[];
  actualHours?: number;
  laborCost?: number;
  externalServiceCost?: number;
  otherCosts?: number;
  requiresFollowup?: boolean;
  followupNotes?: string;
  notes?: string;
}

export interface AddPartDto {
  partId?: string;
  partCode?: string;
  partName: string;
  partDescription?: string;
  quantityRequired: number;
  quantityUsed?: number;
  unitCost?: number;
  fromInventory?: boolean;
}

export interface WorkOrderFilters {
  status?: WorkOrderStatus;
  priority?: WorkOrderPriority;
  maintenanceType?: MaintenanceType;
  assetId?: string;
  projectId?: string;
  assignedToId?: string;
  fromDate?: Date;
  toDate?: Date;
  search?: string;
}

export interface CompleteWorkOrderDto {
  workPerformed: string;
  findings?: string;
  recommendations?: string;
  actualHours: number;
  laborCost: number;
  completedById?: string;
  completedByName?: string;
  completionNotes?: string;
  photosAfter?: string[];
  requiresFollowup?: boolean;
  followupNotes?: string;
}

export class WorkOrderService {
  private workOrderRepository: Repository<WorkOrder>;
  private partRepository: Repository<WorkOrderPart>;
  private historyRepository: Repository<MaintenanceHistory>;
  private planRepository: Repository<MaintenancePlan>;
  private assetRepository: Repository<Asset>;

  constructor(dataSource: DataSource) {
    this.workOrderRepository = dataSource.getRepository(WorkOrder);
    this.partRepository = dataSource.getRepository(WorkOrderPart);
    this.historyRepository = dataSource.getRepository(MaintenanceHistory);
    this.planRepository = dataSource.getRepository(MaintenancePlan);
    this.assetRepository = dataSource.getRepository(Asset);
  }

  /**
   * Generate next work order number
   */
  private async generateWorkOrderNumber(tenantId: string): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `OT-${year}-`;

    const lastOrder = await this.workOrderRepository.findOne({
      where: { tenantId },
      order: { createdAt: 'DESC' },
    });

    let sequence = 1;
    if (lastOrder?.workOrderNumber?.startsWith(prefix)) {
      const lastSeq = parseInt(lastOrder.workOrderNumber.replace(prefix, ''), 10);
      sequence = isNaN(lastSeq) ? 1 : lastSeq + 1;
    }

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

  /**
   * Create a new work order
   */
  async create(tenantId: string, dto: CreateWorkOrderDto, userId?: string): Promise<WorkOrder> {
    // Get asset info
    const asset = await this.assetRepository.findOne({
      where: { id: dto.assetId, tenantId },
    });

    if (!asset) {
      throw new Error('Asset not found');
    }

    const workOrderNumber = await this.generateWorkOrderNumber(tenantId);

    const workOrder = this.workOrderRepository.create({
      tenantId,
      workOrderNumber,
      assetId: dto.assetId,
      assetCode: asset.assetCode,
      assetName: asset.name,
      maintenanceType: dto.maintenanceType,
      priority: dto.priority || WorkOrderPriorityValues.MEDIUM,
      status: WorkOrderStatusValues.DRAFT,
      title: dto.title,
      description: dto.description,
      problemReported: dto.problemReported,
      projectId: dto.projectId || asset.currentProjectId,
      projectName: dto.projectName,
      requestedDate: new Date(),
      scheduledStartDate: dto.scheduledStartDate,
      scheduledEndDate: dto.scheduledEndDate,
      hoursAtWorkOrder: asset.currentHours,
      kilometersAtWorkOrder: asset.currentKilometers,
      assignedToId: dto.assignedToId,
      assignedToName: dto.assignedToName,
      requestedById: userId,
      estimatedHours: dto.estimatedHours,
      activitiesChecklist: dto.activitiesChecklist,
      isScheduled: dto.isScheduled || false,
      scheduleId: dto.scheduleId,
      planId: dto.planId,
      createdBy: userId,
    });

    return this.workOrderRepository.save(workOrder);
  }

  /**
   * Find work order by ID
   */
  async findById(tenantId: string, id: string): Promise<WorkOrder | null> {
    return this.workOrderRepository.findOne({
      where: { id, tenantId },
      relations: ['asset', 'partsUsed'],
    });
  }

  /**
   * Find work order by number
   */
  async findByNumber(tenantId: string, workOrderNumber: string): Promise<WorkOrder | null> {
    return this.workOrderRepository.findOne({
      where: { tenantId, workOrderNumber },
      relations: ['asset', 'partsUsed'],
    });
  }

  /**
   * List work orders with filters
   */
  async findAll(
    tenantId: string,
    filters: WorkOrderFilters = {},
    pagination = { page: 1, limit: 20 }
  ) {
    const queryBuilder = this.workOrderRepository.createQueryBuilder('wo')
      .leftJoinAndSelect('wo.asset', 'asset')
      .where('wo.tenant_id = :tenantId', { tenantId })
      .andWhere('wo.deleted_at IS NULL');

    if (filters.status) {
      queryBuilder.andWhere('wo.status = :status', { status: filters.status });
    }
    if (filters.priority) {
      queryBuilder.andWhere('wo.priority = :priority', { priority: filters.priority });
    }
    if (filters.maintenanceType) {
      queryBuilder.andWhere('wo.maintenance_type = :maintenanceType', { maintenanceType: filters.maintenanceType });
    }
    if (filters.assetId) {
      queryBuilder.andWhere('wo.asset_id = :assetId', { assetId: filters.assetId });
    }
    if (filters.projectId) {
      queryBuilder.andWhere('wo.project_id = :projectId', { projectId: filters.projectId });
    }
    if (filters.assignedToId) {
      queryBuilder.andWhere('wo.assigned_to_id = :assignedToId', { assignedToId: filters.assignedToId });
    }
    if (filters.fromDate) {
      queryBuilder.andWhere('wo.requested_date >= :fromDate', { fromDate: filters.fromDate });
    }
    if (filters.toDate) {
      queryBuilder.andWhere('wo.requested_date <= :toDate', { toDate: filters.toDate });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(wo.work_order_number ILIKE :search OR wo.title ILIKE :search OR wo.asset_name ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (pagination.page - 1) * pagination.limit;

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

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Update work order
   */
  async update(tenantId: string, id: string, dto: UpdateWorkOrderDto, userId?: string): Promise<WorkOrder | null> {
    const workOrder = await this.findById(tenantId, id);
    if (!workOrder) return null;

    // Handle status transitions
    if (dto.status && dto.status !== workOrder.status) {
      this.validateStatusTransition(workOrder.status, dto.status);
      this.applyStatusSideEffects(workOrder, dto.status);
    }

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

    // Recalculate total cost
    workOrder.totalCost =
      (workOrder.laborCost || 0) +
      (workOrder.partsCost || 0) +
      (workOrder.externalServiceCost || 0) +
      (workOrder.otherCosts || 0);

    return this.workOrderRepository.save(workOrder);
  }

  /**
   * Validate status transition
   */
  private validateStatusTransition(from: WorkOrderStatus, to: WorkOrderStatus): void {
    const validTransitions: Record<WorkOrderStatus, WorkOrderStatus[]> = {
      draft: ['scheduled', 'in_progress', 'cancelled'],
      scheduled: ['in_progress', 'cancelled'],
      in_progress: ['on_hold', 'completed', 'cancelled'],
      on_hold: ['in_progress', 'cancelled'],
      completed: [],
      cancelled: [],
    };

    if (!validTransitions[from]?.includes(to)) {
      throw new Error(`Invalid status transition from ${from} to ${to}`);
    }
  }

  /**
   * Apply side effects when status changes
   */
  private applyStatusSideEffects(workOrder: WorkOrder, newStatus: WorkOrderStatus): void {
    const now = new Date();

    switch (newStatus) {
      case 'in_progress':
        if (!workOrder.actualStartDate) {
          workOrder.actualStartDate = now;
        }
        break;
      case 'completed':
        workOrder.actualEndDate = now;
        break;
    }
  }

  /**
   * Start work order (change to in_progress)
   */
  async start(tenantId: string, id: string, userId?: string): Promise<WorkOrder | null> {
    return this.update(tenantId, id, { status: 'in_progress' }, userId);
  }

  /**
   * Complete work order
   */
  async complete(tenantId: string, id: string, dto: CompleteWorkOrderDto, userId?: string): Promise<WorkOrder | null> {
    const workOrder = await this.findById(tenantId, id);
    if (!workOrder) return null;

    // Update work order
    workOrder.status = 'completed';
    workOrder.actualEndDate = new Date();
    workOrder.workPerformed = dto.workPerformed;
    workOrder.findings = dto.findings;
    workOrder.recommendations = dto.recommendations;
    workOrder.actualHours = dto.actualHours;
    workOrder.laborCost = dto.laborCost;
    workOrder.completedById = dto.completedById || userId;
    workOrder.completedByName = dto.completedByName;
    workOrder.completionNotes = dto.completionNotes;
    workOrder.photosAfter = dto.photosAfter;
    workOrder.requiresFollowup = dto.requiresFollowup || false;
    workOrder.followupNotes = dto.followupNotes;
    workOrder.updatedBy = userId;

    // Recalculate total cost
    workOrder.totalCost =
      (workOrder.laborCost || 0) +
      (workOrder.partsCost || 0) +
      (workOrder.externalServiceCost || 0) +
      (workOrder.otherCosts || 0);

    const savedWorkOrder = await this.workOrderRepository.save(workOrder);

    // Create maintenance history record
    await this.historyRepository.save({
      tenantId,
      assetId: workOrder.assetId,
      workOrderId: workOrder.id,
      maintenanceDate: new Date(),
      maintenanceType: workOrder.maintenanceType,
      description: workOrder.title,
      workPerformed: dto.workPerformed,
      hoursAtMaintenance: workOrder.hoursAtWorkOrder,
      kilometersAtMaintenance: workOrder.kilometersAtWorkOrder,
      laborCost: workOrder.laborCost,
      partsCost: workOrder.partsCost,
      totalCost: workOrder.totalCost,
      performedById: dto.completedById || userId,
      performedByName: dto.completedByName,
      createdBy: userId,
    });

    return savedWorkOrder;
  }

  /**
   * Add part to work order
   */
  async addPart(tenantId: string, workOrderId: string, dto: AddPartDto, userId?: string): Promise<WorkOrderPart> {
    const workOrder = await this.findById(tenantId, workOrderId);
    if (!workOrder) {
      throw new Error('Work order not found');
    }

    const totalCost = dto.unitCost && dto.quantityUsed
      ? dto.unitCost * dto.quantityUsed
      : undefined;

    const part = this.partRepository.create({
      tenantId,
      workOrderId,
      partId: dto.partId,
      partCode: dto.partCode,
      partName: dto.partName,
      partDescription: dto.partDescription,
      quantityRequired: dto.quantityRequired,
      quantityUsed: dto.quantityUsed,
      unitCost: dto.unitCost,
      totalCost,
      fromInventory: dto.fromInventory || false,
      createdBy: userId,
    });

    const savedPart = await this.partRepository.save(part);

    // Update parts cost in work order
    await this.recalculatePartsCost(workOrderId);

    return savedPart;
  }

  /**
   * Recalculate parts cost for work order
   */
  private async recalculatePartsCost(workOrderId: string): Promise<void> {
    const parts = await this.partRepository.find({ where: { workOrderId } });

    const partsCost = parts.reduce((sum, part) => sum + (Number(part.totalCost) || 0), 0);
    const partsCount = parts.length;

    await this.workOrderRepository.update(
      { id: workOrderId },
      {
        partsCost,
        partsUsedCount: partsCount,
        totalCost: () => `labor_cost + ${partsCost} + external_service_cost + other_costs`,
      }
    );
  }

  /**
   * Get work order statistics
   */
  async getStatistics(tenantId: string): Promise<{
    total: number;
    byStatus: Record<string, number>;
    byType: Record<string, number>;
    pendingCount: number;
    inProgressCount: number;
    completedThisMonth: number;
    totalCostThisMonth: number;
  }> {
    const now = new Date();
    const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);

    const [total, byStatusRaw, byTypeRaw, pendingCount, inProgressCount, completedThisMonth, costResult] =
      await Promise.all([
        this.workOrderRepository.count({ where: { tenantId, deletedAt: IsNull() } }),

        this.workOrderRepository.createQueryBuilder('wo')
          .select('wo.status', 'status')
          .addSelect('COUNT(*)', 'count')
          .where('wo.tenant_id = :tenantId', { tenantId })
          .andWhere('wo.deleted_at IS NULL')
          .groupBy('wo.status')
          .getRawMany(),

        this.workOrderRepository.createQueryBuilder('wo')
          .select('wo.maintenance_type', 'type')
          .addSelect('COUNT(*)', 'count')
          .where('wo.tenant_id = :tenantId', { tenantId })
          .andWhere('wo.deleted_at IS NULL')
          .groupBy('wo.maintenance_type')
          .getRawMany(),

        this.workOrderRepository.count({
          where: { tenantId, status: 'draft' as WorkOrderStatus, deletedAt: IsNull() },
        }),

        this.workOrderRepository.count({
          where: { tenantId, status: 'in_progress' as WorkOrderStatus, deletedAt: IsNull() },
        }),

        this.workOrderRepository.createQueryBuilder('wo')
          .where('wo.tenant_id = :tenantId', { tenantId })
          .andWhere('wo.status = :status', { status: 'completed' as WorkOrderStatus })
          .andWhere('wo.actual_end_date >= :startOfMonth', { startOfMonth })
          .getCount(),

        this.workOrderRepository.createQueryBuilder('wo')
          .select('SUM(wo.total_cost)', 'total')
          .where('wo.tenant_id = :tenantId', { tenantId })
          .andWhere('wo.status = :status', { status: 'completed' as WorkOrderStatus })
          .andWhere('wo.actual_end_date >= :startOfMonth', { startOfMonth })
          .getRawOne(),
      ]);

    const byStatus: Record<string, number> = {};
    byStatusRaw.forEach((row: any) => {
      byStatus[row.status] = parseInt(row.count, 10);
    });

    const byType: Record<string, number> = {};
    byTypeRaw.forEach((row: any) => {
      byType[row.type] = parseInt(row.count, 10);
    });

    return {
      total,
      byStatus,
      byType,
      pendingCount,
      inProgressCount,
      completedThisMonth,
      totalCostThisMonth: parseFloat(costResult?.total) || 0,
    };
  }

  /**
   * Get work orders grouped by status (for Kanban)
   */
  async getByStatus(tenantId: string): Promise<Record<WorkOrderStatus, WorkOrder[]>> {
    const workOrders = await this.workOrderRepository.find({
      where: { tenantId, deletedAt: IsNull() },
      relations: ['asset'],
      order: { requestedDate: 'DESC' },
    });

    const grouped: Record<WorkOrderStatus, WorkOrder[]> = {
      draft: [],
      scheduled: [],
      in_progress: [],
      on_hold: [],
      completed: [],
      cancelled: [],
    };

    for (const wo of workOrders) {
      grouped[wo.status].push(wo);
    }

    return grouped;
  }

  /**
   * Put work order on hold
   */
  async hold(tenantId: string, id: string, reason?: string, userId?: string): Promise<WorkOrder | null> {
    const workOrder = await this.findById(tenantId, id);
    if (!workOrder) return null;

    this.validateStatusTransition(workOrder.status, 'on_hold' as WorkOrderStatus);

    workOrder.status = 'on_hold' as WorkOrderStatus;
    workOrder.notes = reason ? `${workOrder.notes || ''}\n[ON HOLD] ${reason}` : workOrder.notes;
    workOrder.updatedBy = userId;

    return this.workOrderRepository.save(workOrder);
  }

  /**
   * Resume work order from hold
   */
  async resume(tenantId: string, id: string, userId?: string): Promise<WorkOrder | null> {
    const workOrder = await this.findById(tenantId, id);
    if (!workOrder) return null;

    this.validateStatusTransition(workOrder.status, 'in_progress' as WorkOrderStatus);

    workOrder.status = 'in_progress' as WorkOrderStatus;
    workOrder.updatedBy = userId;

    return this.workOrderRepository.save(workOrder);
  }

  /**
   * Cancel work order
   */
  async cancel(tenantId: string, id: string, reason?: string, userId?: string): Promise<WorkOrder | null> {
    const workOrder = await this.findById(tenantId, id);
    if (!workOrder) return null;

    this.validateStatusTransition(workOrder.status, 'cancelled' as WorkOrderStatus);

    workOrder.status = 'cancelled' as WorkOrderStatus;
    workOrder.notes = reason ? `${workOrder.notes || ''}\n[CANCELLED] ${reason}` : workOrder.notes;
    workOrder.updatedBy = userId;

    return this.workOrderRepository.save(workOrder);
  }

  /**
   * Soft delete work order
   */
  async delete(tenantId: string, id: string, userId?: string): Promise<boolean> {
    const result = await this.workOrderRepository.update(
      { id, tenantId },
      { deletedAt: new Date(), updatedBy: userId }
    );
    return (result.affected ?? 0) > 0;
  }

  /**
   * Get parts for a work order
   */
  async getParts(tenantId: string, workOrderId: string): Promise<WorkOrderPart[]> {
    return this.partRepository.find({
      where: { tenantId, workOrderId },
      order: { createdAt: 'ASC' },
    });
  }

  /**
   * Update a part
   */
  async updatePart(tenantId: string, partId: string, dto: Partial<AddPartDto>, _userId?: string): Promise<WorkOrderPart | null> {
    const part = await this.partRepository.findOne({ where: { id: partId, tenantId } });
    if (!part) return null;

    if (dto.quantityUsed !== undefined) {
      part.quantityUsed = dto.quantityUsed;
    }
    if (dto.unitCost !== undefined) {
      part.unitCost = dto.unitCost;
    }
    if (part.unitCost && part.quantityUsed) {
      part.totalCost = part.unitCost * part.quantityUsed;
    }

    const savedPart = await this.partRepository.save(part);
    await this.recalculatePartsCost(part.workOrderId);

    return savedPart;
  }

  /**
   * Remove a part from work order
   */
  async removePart(tenantId: string, partId: string, _userId?: string): Promise<boolean> {
    const part = await this.partRepository.findOne({ where: { id: partId, tenantId } });
    if (!part) return false;

    const workOrderId = part.workOrderId;
    await this.partRepository.remove(part);
    await this.recalculatePartsCost(workOrderId);

    return true;
  }

  /**
   * Get overdue work orders
   */
  async getOverdue(tenantId: string): Promise<WorkOrder[]> {
    const now = new Date();

    return this.workOrderRepository.find({
      where: {
        tenantId,
        deletedAt: undefined,
        status: In(['draft' as WorkOrderStatus, 'scheduled' as WorkOrderStatus, 'in_progress' as WorkOrderStatus]),
        scheduledEndDate: LessThanOrEqual(now),
      },
      relations: ['asset'],
      order: { scheduledEndDate: 'ASC' },
    });
  }

  /**
   * Get maintenance plans
   */
  async getMaintenancePlans(tenantId: string, assetId?: string): Promise<MaintenancePlan[]> {
    const where: any = { tenantId, isActive: true, deletedAt: undefined };
    if (assetId) {
      where.assetId = assetId;
    }

    return this.planRepository.find({
      where,
      relations: ['asset', 'category'],
      order: { name: 'ASC' },
    });
  }

  /**
   * Create a maintenance plan
   */
  async createMaintenancePlan(tenantId: string, data: Partial<MaintenancePlan>, userId?: string): Promise<MaintenancePlan> {
    const plan = this.planRepository.create({
      tenantId,
      ...data,
      isActive: true,
      createdBy: userId,
    });

    return this.planRepository.save(plan);
  }

  /**
   * Generate work orders from a maintenance plan
   */
  async generateFromPlan(tenantId: string, planId: string, userId?: string): Promise<WorkOrder[]> {
    const plan = await this.planRepository.findOne({
      where: { id: planId, tenantId },
      relations: ['asset'],
    });

    if (!plan) {
      throw new Error('Maintenance plan not found');
    }

    const assets: Asset[] = [];

    if (plan.assetId && plan.asset) {
      assets.push(plan.asset);
    } else if (plan.categoryId || plan.assetType) {
      const queryBuilder = this.assetRepository.createQueryBuilder('asset')
        .where('asset.tenant_id = :tenantId', { tenantId })
        .andWhere('asset.deleted_at IS NULL');

      if (plan.categoryId) {
        queryBuilder.andWhere('asset.category_id = :categoryId', { categoryId: plan.categoryId });
      }
      if (plan.assetType) {
        queryBuilder.andWhere('asset.asset_type = :assetType', { assetType: plan.assetType });
      }

      const foundAssets = await queryBuilder.getMany();
      assets.push(...foundAssets);
    }

    const createdOrders: WorkOrder[] = [];

    for (const asset of assets) {
      const workOrder = await this.create(tenantId, {
        assetId: asset.id,
        maintenanceType: plan.maintenanceType as MaintenanceType,
        title: `[${plan.planCode}] ${plan.name}`,
        description: plan.description,
        estimatedHours: plan.estimatedDurationHours ? Number(plan.estimatedDurationHours) : undefined,
        activitiesChecklist: plan.activities,
        isScheduled: true,
        planId: plan.id,
      }, userId);

      createdOrders.push(workOrder);
    }

    return createdOrders;
  }
}
