/**
 * Service Order Service
 * Mecánicas Diesel - ERP Suite
 *
 * Business logic for service orders management.
 */

import { Repository, DataSource, FindOptionsWhere, ILike } from 'typeorm';
import {
  ServiceOrder,
  ServiceOrderStatus,
  ServiceOrderPriority,
} from '../entities/service-order.entity';
import { OrderItem, OrderItemType, OrderItemStatus } from '../entities/order-item.entity';

// DTOs
export interface CreateServiceOrderDto {
  customerId: string;
  vehicleId: string;
  customerSymptoms?: string;
  priority?: ServiceOrderPriority;
  promisedAt?: Date;
  assignedTo?: string;
  bayId?: string;
  odometerIn?: number;
  internalNotes?: string;
}

export interface UpdateServiceOrderDto {
  status?: ServiceOrderStatus;
  priority?: ServiceOrderPriority;
  assignedTo?: string;
  bayId?: string;
  promisedAt?: Date;
  odometerOut?: number;
  customerSymptoms?: string;
  internalNotes?: string;
  customerNotes?: string;
}

export interface AddOrderItemDto {
  itemType: OrderItemType;
  description: string;
  quantity: number;
  unitPrice: number;
  discountPct?: number;
  serviceId?: string;
  partId?: string;
  estimatedHours?: number;
  notes?: string;
}

export interface ServiceOrderFilters {
  status?: ServiceOrderStatus;
  priority?: ServiceOrderPriority;
  customerId?: string;
  vehicleId?: string;
  assignedTo?: string;
  bayId?: string;
  search?: string;
  fromDate?: Date;
  toDate?: Date;
}

export interface PaginationOptions {
  page: number;
  limit: number;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export class ServiceOrderService {
  private orderRepository: Repository<ServiceOrder>;
  private itemRepository: Repository<OrderItem>;

  constructor(private dataSource: DataSource) {
    this.orderRepository = dataSource.getRepository(ServiceOrder);
    this.itemRepository = dataSource.getRepository(OrderItem);
  }

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

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

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

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

  /**
   * Create a new service order
   */
  async create(tenantId: string, dto: CreateServiceOrderDto, userId?: string): Promise<ServiceOrder> {
    const orderNumber = await this.generateOrderNumber(tenantId);

    const order = this.orderRepository.create({
      tenantId,
      orderNumber,
      customerId: dto.customerId,
      vehicleId: dto.vehicleId,
      customerSymptoms: dto.customerSymptoms,
      priority: dto.priority || ServiceOrderPriority.NORMAL,
      status: ServiceOrderStatus.RECEIVED,
      promisedAt: dto.promisedAt,
      assignedTo: dto.assignedTo,
      bayId: dto.bayId,
      odometerIn: dto.odometerIn,
      internalNotes: dto.internalNotes,
      createdBy: userId,
      receivedAt: new Date(),
    });

    return this.orderRepository.save(order);
  }

  /**
   * Find order by ID
   */
  async findById(tenantId: string, id: string): Promise<ServiceOrder | null> {
    return this.orderRepository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Find order by order number
   */
  async findByOrderNumber(tenantId: string, orderNumber: string): Promise<ServiceOrder | null> {
    return this.orderRepository.findOne({
      where: { tenantId, orderNumber },
    });
  }

  /**
   * List orders with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: ServiceOrderFilters = {},
    pagination: PaginationOptions = { page: 1, limit: 20 }
  ): Promise<PaginatedResult<ServiceOrder>> {
    const where: FindOptionsWhere<ServiceOrder> = { tenantId };

    if (filters.status) where.status = filters.status;
    if (filters.priority) where.priority = filters.priority;
    if (filters.customerId) where.customerId = filters.customerId;
    if (filters.vehicleId) where.vehicleId = filters.vehicleId;
    if (filters.assignedTo) where.assignedTo = filters.assignedTo;
    if (filters.bayId) where.bayId = filters.bayId;

    const queryBuilder = this.orderRepository.createQueryBuilder('order')
      .where('order.tenant_id = :tenantId', { tenantId });

    if (filters.status) {
      queryBuilder.andWhere('order.status = :status', { status: filters.status });
    }
    if (filters.priority) {
      queryBuilder.andWhere('order.priority = :priority', { priority: filters.priority });
    }
    if (filters.customerId) {
      queryBuilder.andWhere('order.customer_id = :customerId', { customerId: filters.customerId });
    }
    if (filters.vehicleId) {
      queryBuilder.andWhere('order.vehicle_id = :vehicleId', { vehicleId: filters.vehicleId });
    }
    if (filters.assignedTo) {
      queryBuilder.andWhere('order.assigned_to = :assignedTo', { assignedTo: filters.assignedTo });
    }
    if (filters.fromDate) {
      queryBuilder.andWhere('order.received_at >= :fromDate', { fromDate: filters.fromDate });
    }
    if (filters.toDate) {
      queryBuilder.andWhere('order.received_at <= :toDate', { toDate: filters.toDate });
    }
    if (filters.search) {
      queryBuilder.andWhere(
        '(order.order_number ILIKE :search OR order.customer_symptoms ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

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

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

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

  /**
   * Update service order
   */
  async update(
    tenantId: string,
    id: string,
    dto: UpdateServiceOrderDto
  ): Promise<ServiceOrder | null> {
    const order = await this.findById(tenantId, id);
    if (!order) return null;

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

    Object.assign(order, dto);
    return this.orderRepository.save(order);
  }

  /**
   * Validate status transition
   */
  private validateStatusTransition(from: ServiceOrderStatus, to: ServiceOrderStatus): void {
    const validTransitions: Record<ServiceOrderStatus, ServiceOrderStatus[]> = {
      [ServiceOrderStatus.RECEIVED]: [ServiceOrderStatus.DIAGNOSED, ServiceOrderStatus.CANCELLED],
      [ServiceOrderStatus.DIAGNOSED]: [ServiceOrderStatus.QUOTED, ServiceOrderStatus.IN_PROGRESS, ServiceOrderStatus.CANCELLED],
      [ServiceOrderStatus.QUOTED]: [ServiceOrderStatus.APPROVED, ServiceOrderStatus.CANCELLED],
      [ServiceOrderStatus.APPROVED]: [ServiceOrderStatus.IN_PROGRESS, ServiceOrderStatus.CANCELLED],
      [ServiceOrderStatus.IN_PROGRESS]: [ServiceOrderStatus.WAITING_PARTS, ServiceOrderStatus.COMPLETED, ServiceOrderStatus.CANCELLED],
      [ServiceOrderStatus.WAITING_PARTS]: [ServiceOrderStatus.IN_PROGRESS, ServiceOrderStatus.CANCELLED],
      [ServiceOrderStatus.COMPLETED]: [ServiceOrderStatus.DELIVERED],
      [ServiceOrderStatus.DELIVERED]: [],
      [ServiceOrderStatus.CANCELLED]: [],
    };

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

  /**
   * Apply side effects when status changes
   */
  private applyStatusSideEffects(order: ServiceOrder, newStatus: ServiceOrderStatus): void {
    const now = new Date();

    switch (newStatus) {
      case ServiceOrderStatus.IN_PROGRESS:
        if (!order.startedAt) order.startedAt = now;
        break;
      case ServiceOrderStatus.COMPLETED:
        order.completedAt = now;
        break;
      case ServiceOrderStatus.DELIVERED:
        order.deliveredAt = now;
        break;
    }
  }

  /**
   * Add item to order
   */
  async addItem(tenantId: string, orderId: string, dto: AddOrderItemDto): Promise<OrderItem | null> {
    const order = await this.findById(tenantId, orderId);
    if (!order) return null;

    const subtotal = dto.quantity * dto.unitPrice * (1 - (dto.discountPct || 0) / 100);

    const item = this.itemRepository.create({
      orderId,
      itemType: dto.itemType,
      description: dto.description,
      quantity: dto.quantity,
      unitPrice: dto.unitPrice,
      discountPct: dto.discountPct || 0,
      subtotal,
      serviceId: dto.serviceId,
      partId: dto.partId,
      estimatedHours: dto.estimatedHours,
      notes: dto.notes,
      status: OrderItemStatus.PENDING,
    });

    const savedItem = await this.itemRepository.save(item);

    // Recalculate totals
    await this.recalculateTotals(orderId);

    return savedItem;
  }

  /**
   * Get order items
   */
  async getItems(orderId: string): Promise<OrderItem[]> {
    return this.itemRepository.find({
      where: { orderId },
      order: { sortOrder: 'ASC', createdAt: 'ASC' },
    });
  }

  /**
   * Update order item
   */
  async updateItem(
    itemId: string,
    dto: Partial<AddOrderItemDto>
  ): Promise<OrderItem | null> {
    const item = await this.itemRepository.findOne({ where: { id: itemId } });
    if (!item) return null;

    if (dto.quantity !== undefined || dto.unitPrice !== undefined || dto.discountPct !== undefined) {
      const quantity = dto.quantity ?? item.quantity;
      const unitPrice = dto.unitPrice ?? item.unitPrice;
      const discountPct = dto.discountPct ?? item.discountPct;
      item.subtotal = quantity * unitPrice * (1 - discountPct / 100);
    }

    Object.assign(item, dto);
    const savedItem = await this.itemRepository.save(item);

    // Recalculate totals
    await this.recalculateTotals(item.orderId);

    return savedItem;
  }

  /**
   * Remove order item
   */
  async removeItem(itemId: string): Promise<boolean> {
    const item = await this.itemRepository.findOne({ where: { id: itemId } });
    if (!item) return false;

    const orderId = item.orderId;
    await this.itemRepository.remove(item);

    // Recalculate totals
    await this.recalculateTotals(orderId);

    return true;
  }

  /**
   * Recalculate order totals
   */
  private async recalculateTotals(orderId: string): Promise<void> {
    const items = await this.getItems(orderId);

    let laborTotal = 0;
    let partsTotal = 0;

    for (const item of items) {
      if (item.itemType === OrderItemType.SERVICE) {
        laborTotal += Number(item.subtotal);
      } else {
        partsTotal += Number(item.subtotal);
      }
    }

    const order = await this.orderRepository.findOne({ where: { id: orderId } });
    if (!order) return;

    order.laborTotal = laborTotal;
    order.partsTotal = partsTotal;

    const subtotal = laborTotal + partsTotal;
    const discountAmount = subtotal * (Number(order.discountPercent) / 100);
    order.discountAmount = discountAmount;

    const taxableAmount = subtotal - discountAmount;
    order.tax = taxableAmount * 0.16; // 16% IVA México

    order.grandTotal = taxableAmount + order.tax;

    await this.orderRepository.save(order);
  }

  /**
   * Get orders by status (for Kanban board)
   */
  async getOrdersByStatus(tenantId: string): Promise<Record<ServiceOrderStatus, ServiceOrder[]>> {
    const orders = await this.orderRepository.find({
      where: { tenantId },
      order: { receivedAt: 'DESC' },
    });

    const grouped: Record<ServiceOrderStatus, ServiceOrder[]> = {
      [ServiceOrderStatus.RECEIVED]: [],
      [ServiceOrderStatus.DIAGNOSED]: [],
      [ServiceOrderStatus.QUOTED]: [],
      [ServiceOrderStatus.APPROVED]: [],
      [ServiceOrderStatus.IN_PROGRESS]: [],
      [ServiceOrderStatus.WAITING_PARTS]: [],
      [ServiceOrderStatus.COMPLETED]: [],
      [ServiceOrderStatus.DELIVERED]: [],
      [ServiceOrderStatus.CANCELLED]: [],
    };

    for (const order of orders) {
      grouped[order.status].push(order);
    }

    return grouped;
  }

  /**
   * Get dashboard statistics
   */
  async getDashboardStats(tenantId: string): Promise<{
    totalOrders: number;
    pendingOrders: number;
    inProgressOrders: number;
    completedToday: number;
    totalRevenue: number;
    averageTicket: number;
  }> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const [
      totalOrders,
      pendingOrders,
      inProgressOrders,
      completedToday,
      revenueResult,
    ] = await Promise.all([
      this.orderRepository.count({ where: { tenantId } }),
      this.orderRepository.count({
        where: { tenantId, status: ServiceOrderStatus.RECEIVED },
      }),
      this.orderRepository.count({
        where: { tenantId, status: ServiceOrderStatus.IN_PROGRESS },
      }),
      this.orderRepository.createQueryBuilder('order')
        .where('order.tenant_id = :tenantId', { tenantId })
        .andWhere('order.status = :status', { status: ServiceOrderStatus.COMPLETED })
        .andWhere('order.completed_at >= :today', { today })
        .getCount(),
      this.orderRepository.createQueryBuilder('order')
        .select('SUM(order.grand_total)', 'total')
        .where('order.tenant_id = :tenantId', { tenantId })
        .andWhere('order.status IN (:...statuses)', {
          statuses: [ServiceOrderStatus.COMPLETED, ServiceOrderStatus.DELIVERED],
        })
        .getRawOne(),
    ]);

    const totalRevenue = parseFloat(revenueResult?.total) || 0;
    const completedCount = await this.orderRepository.count({
      where: {
        tenantId,
        status: ServiceOrderStatus.COMPLETED,
      },
    });

    return {
      totalOrders,
      pendingOrders,
      inProgressOrders,
      completedToday,
      totalRevenue,
      averageTicket: completedCount > 0 ? totalRevenue / completedCount : 0,
    };
  }
}
