/**
 * @fileoverview Unit tests for OrdenesTransporteService
 * Tests cover CRUD operations, business logic (asignarUnidad, calcularTarifa, cambiarEstado),
 * and validation scenarios.
 *
 * Schema: transport
 * Module: MAI-003 (Ordenes de Transporte)
 */

import { jest, describe, it, expect, beforeEach } from '@jest/globals';
import { Repository } from 'typeorm';
import {
  OrdenTransporte,
  EstadoOrdenTransporte,
  ModalidadServicio,
  TipoCarga,
} from '../entities/index.js';
import {
  OrdenesTransporteService,
  CreateOrdenTransporteDto,
  UpdateOrdenTransporteDto,
  AsignarUnidadDto,
  ServiceContext,
} from '../services/ordenes-transporte.service.js';

describe('OrdenesTransporteService', () => {
  let service: OrdenesTransporteService;
  let mockRepository: any;
  let mockManager: any;

  // Test fixtures - UUIDs
  const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
  const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
  const mockOrderId = '550e8400-e29b-41d4-a716-446655440010';
  const mockClienteId = '550e8400-e29b-41d4-a716-446655440020';
  const mockShipperId = '550e8400-e29b-41d4-a716-446655440021';
  const mockConsigneeId = '550e8400-e29b-41d4-a716-446655440022';
  const mockUnidadId = '550e8400-e29b-41d4-a716-446655440030';
  const mockOperadorId = '550e8400-e29b-41d4-a716-446655440031';
  const mockViajeId = '550e8400-e29b-41d4-a716-446655440040';
  const mockTarifaId = '550e8400-e29b-41d4-a716-446655440050';

  // Mock OrdenTransporte entity factory
  const createMockOrden = (overrides: Partial<OrdenTransporte> = {}): OrdenTransporte => ({
    id: mockOrderId,
    tenantId: mockTenantId,
    codigo: 'OT-202602-000001',
    numeroOt: 'OT-202602-000001',
    referenciaCliente: 'REF-CLIENTE-001',
    clienteId: mockClienteId,
    shipperId: mockShipperId,
    shipperNombre: 'Shipper Test SA',
    consigneeId: mockConsigneeId,
    consigneeNombre: 'Consignee Test SA',
    origenDireccion: 'Av. Insurgentes Sur 1000, CDMX',
    origenCiudad: 'Ciudad de Mexico',
    origenEstado: 'CDMX',
    origenCodigoPostal: '03100',
    origenLatitud: 19.3984,
    origenLongitud: -99.1576,
    origenContacto: 'Juan Perez',
    origenTelefono: '5551234567',
    destinoDireccion: 'Blvd. Puerta de Hierro 5000, Guadalajara',
    destinoCiudad: 'Guadalajara',
    destinoEstado: 'Jalisco',
    destinoCodigoPostal: '44510',
    destinoLatitud: 20.6597,
    destinoLongitud: -103.3496,
    destinoContacto: 'Maria Garcia',
    destinoTelefono: '3331234567',
    fechaRecoleccion: null as any,
    fechaRecoleccionProgramada: new Date('2026-02-10T08:00:00Z'),
    fechaEntregaProgramada: new Date('2026-02-11T18:00:00Z'),
    tipoCarga: TipoCarga.GENERAL,
    descripcionCarga: 'Mercancia general paletizada',
    pesoKg: 15000,
    volumenM3: 45,
    piezas: 50,
    pallets: 20,
    valorDeclarado: 500000,
    requiereTemperatura: false,
    temperaturaMin: null as any,
    temperaturaMax: null as any,
    requiereGps: true,
    requiereEscolta: false,
    instruccionesEspeciales: 'Entregar en horario laboral',
    modalidadServicio: ModalidadServicio.FTL,
    tarifaId: mockTarifaId,
    tarifaBase: 25000,
    recargos: 2000,
    descuentos: 500,
    subtotal: 26500,
    iva: 4240,
    total: 30740,
    estado: EstadoOrdenTransporte.BORRADOR,
    viajeId: null as any,
    embarqueId: null as any,
    observaciones: 'Orden de prueba',
    createdAt: new Date('2026-02-01T10:00:00Z'),
    createdById: mockUserId,
    updatedAt: new Date('2026-02-01T10:00:00Z'),
    updatedById: null as any,
    deletedAt: null as any,
    ...overrides,
  });

  const mockCreateDto: CreateOrdenTransporteDto = {
    clienteId: mockClienteId,
    referenciaCliente: 'REF-CLIENTE-001',
    modalidadServicio: ModalidadServicio.FTL,
    tipoCarga: TipoCarga.GENERAL,
    shipperId: mockShipperId,
    shipperNombre: 'Shipper Test SA',
    consigneeId: mockConsigneeId,
    consigneeNombre: 'Consignee Test SA',
    origenDireccion: 'Av. Insurgentes Sur 1000, CDMX',
    origenCiudad: 'Ciudad de Mexico',
    origenEstado: 'CDMX',
    destinoDireccion: 'Blvd. Puerta de Hierro 5000, Guadalajara',
    destinoCiudad: 'Guadalajara',
    destinoEstado: 'Jalisco',
    descripcionCarga: 'Mercancia general',
    pesoKg: 15000,
    tarifaBase: 25000,
  };

  const mockServiceContext: ServiceContext = {
    tenantId: mockTenantId,
    userId: mockUserId,
    correlationId: 'test-correlation-id',
  };

  beforeEach(() => {
    jest.clearAllMocks();

    // Setup mock manager for raw queries
    mockManager = {
      query: jest.fn(),
    };

    // Setup mock repository
    mockRepository = {
      create: jest.fn(),
      save: jest.fn(),
      findOne: jest.fn(),
      find: jest.fn(),
      findAndCount: jest.fn(),
      count: jest.fn(),
      update: jest.fn(),
      softDelete: jest.fn(),
      manager: mockManager,
    };

    // Create service instance with mocked repository
    service = new OrdenesTransporteService(mockRepository as Repository<OrdenTransporte>);
  });

  // ===========================================================================
  // CRUD OPERATIONS
  // ===========================================================================

  describe('CRUD Operations', () => {
    describe('create()', () => {
      it('should create a new transport order with BORRADOR state', async () => {
        const mockOrden = createMockOrden();
        mockRepository.count.mockResolvedValue(0);
        mockRepository.create.mockReturnValue(mockOrden);
        mockRepository.save.mockResolvedValue(mockOrden);

        const result = await service.create(mockTenantId, mockCreateDto, mockUserId);

        expect(mockRepository.create).toHaveBeenCalledWith(
          expect.objectContaining({
            tenantId: mockTenantId,
            clienteId: mockCreateDto.clienteId,
            estado: EstadoOrdenTransporte.BORRADOR,
            createdById: mockUserId,
          })
        );
        expect(mockRepository.save).toHaveBeenCalled();
        expect(result).toBeDefined();
        expect(result.id).toBe(mockOrderId);
      });

      it('should calculate totals correctly on create', async () => {
        const mockOrden = createMockOrden();
        mockRepository.count.mockResolvedValue(0);
        mockRepository.create.mockReturnValue(mockOrden);
        mockRepository.save.mockResolvedValue(mockOrden);

        const dtoWithTarifa: CreateOrdenTransporteDto = {
          ...mockCreateDto,
          tarifaBase: 10000,
          recargos: 1000,
          descuentos: 500,
        };

        await service.create(mockTenantId, dtoWithTarifa, mockUserId);

        // Subtotal = 10000 + 1000 - 500 = 10500
        // IVA = 10500 * 0.16 = 1680
        // Total = 10500 + 1680 = 12180
        expect(mockRepository.create).toHaveBeenCalledWith(
          expect.objectContaining({
            subtotal: 10500,
            iva: 1680,
            total: 12180,
          })
        );
      });

      it('should generate unique codigo for the order', async () => {
        const mockOrden = createMockOrden();
        mockRepository.count.mockResolvedValue(5); // 5 existing orders
        mockRepository.create.mockReturnValue(mockOrden);
        mockRepository.save.mockResolvedValue(mockOrden);

        await service.create(mockTenantId, mockCreateDto, mockUserId);

        expect(mockRepository.create).toHaveBeenCalledWith(
          expect.objectContaining({
            codigo: expect.stringMatching(/^OT-\d{6}-\d{6}$/),
          })
        );
      });
    });

    describe('findAll()', () => {
      it('should return paginated list of orders', async () => {
        const mockOrdenes = [createMockOrden(), createMockOrden({ id: 'order-2' })];
        mockRepository.findAndCount.mockResolvedValue([mockOrdenes, 2]);

        const result = await service.findAll(mockTenantId, { limit: 50, offset: 0 });

        expect(mockRepository.findAndCount).toHaveBeenCalled();
        expect(result.data).toHaveLength(2);
        expect(result.total).toBe(2);
        expect(result.page).toBe(1);
        expect(result.pageSize).toBe(50);
      });

      it('should filter by estado', async () => {
        const mockOrdenes = [createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA })];
        mockRepository.findAndCount.mockResolvedValue([mockOrdenes, 1]);

        const result = await service.findAll(mockTenantId, {
          estado: EstadoOrdenTransporte.CONFIRMADA,
        });

        expect(mockRepository.findAndCount).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.arrayContaining([
              expect.objectContaining({
                estado: EstadoOrdenTransporte.CONFIRMADA,
              }),
            ]),
          })
        );
        expect(result.data[0].estado).toBe(EstadoOrdenTransporte.CONFIRMADA);
      });

      it('should filter by clienteId', async () => {
        const mockOrdenes = [createMockOrden()];
        mockRepository.findAndCount.mockResolvedValue([mockOrdenes, 1]);

        await service.findAll(mockTenantId, { clienteId: mockClienteId });

        expect(mockRepository.findAndCount).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.arrayContaining([
              expect.objectContaining({
                clienteId: mockClienteId,
              }),
            ]),
          })
        );
      });

      it('should filter by search term across multiple fields', async () => {
        mockRepository.findAndCount.mockResolvedValue([[], 0]);

        await service.findAll(mockTenantId, { search: 'TEST' });

        // Should create multiple where clauses for search
        expect(mockRepository.findAndCount).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.arrayContaining([
              expect.objectContaining({ codigo: expect.anything() }),
              expect.objectContaining({ shipperNombre: expect.anything() }),
            ]),
          })
        );
      });

      it('should filter by date range', async () => {
        const fechaDesde = new Date('2026-02-01');
        const fechaHasta = new Date('2026-02-28');
        mockRepository.findAndCount.mockResolvedValue([[], 0]);

        await service.findAll(mockTenantId, { fechaDesde, fechaHasta });

        expect(mockRepository.findAndCount).toHaveBeenCalled();
      });

      it('should support ordering by different fields', async () => {
        mockRepository.findAndCount.mockResolvedValue([[], 0]);

        await service.findAll(mockTenantId, {
          orderBy: 'fechaRecoleccion',
          orderDir: 'ASC',
        });

        expect(mockRepository.findAndCount).toHaveBeenCalledWith(
          expect.objectContaining({
            order: { fechaRecoleccionProgramada: 'ASC' },
          })
        );
      });
    });

    describe('findOne() / findById()', () => {
      it('should return order with relations when found', async () => {
        const mockOrden = createMockOrden();
        mockRepository.findOne.mockResolvedValue(mockOrden);

        const result = await service.findById(mockTenantId, mockOrderId);

        expect(mockRepository.findOne).toHaveBeenCalledWith({
          where: { id: mockOrderId, tenantId: mockTenantId },
        });
        expect(result).toBeDefined();
        expect(result?.id).toBe(mockOrderId);
      });

      it('should return null when order not found', async () => {
        mockRepository.findOne.mockResolvedValue(null);

        const result = await service.findById(mockTenantId, 'non-existent-id');

        expect(result).toBeNull();
      });

      it('should enforce tenant isolation', async () => {
        const mockOrden = createMockOrden();
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await service.findById(mockTenantId, mockOrderId);

        expect(mockRepository.findOne).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              tenantId: mockTenantId,
            }),
          })
        );
      });
    });

    describe('update()', () => {
      it('should update order fields when in allowed state', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockResolvedValue({ ...mockOrden, shipperNombre: 'Updated Shipper' });

        const updateDto: UpdateOrdenTransporteDto = {
          shipperNombre: 'Updated Shipper',
        };

        const result = await service.update(mockTenantId, mockOrderId, updateDto, mockUserId);

        expect(mockRepository.save).toHaveBeenCalled();
        expect(result?.shipperNombre).toBe('Updated Shipper');
      });

      it('should reject updates in final states (ENTREGADA)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.ENTREGADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        const updateDto: UpdateOrdenTransporteDto = { shipperNombre: 'New Name' };

        await expect(
          service.update(mockTenantId, mockOrderId, updateDto, mockUserId)
        ).rejects.toThrow(/No se puede modificar una OT en estado ENTREGADA/);
      });

      it('should reject updates in final states (FACTURADA)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.FACTURADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        const updateDto: UpdateOrdenTransporteDto = { shipperNombre: 'New Name' };

        await expect(
          service.update(mockTenantId, mockOrderId, updateDto, mockUserId)
        ).rejects.toThrow(/No se puede modificar una OT en estado FACTURADA/);
      });

      it('should reject updates in final states (CANCELADA)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CANCELADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        const updateDto: UpdateOrdenTransporteDto = { shipperNombre: 'New Name' };

        await expect(
          service.update(mockTenantId, mockOrderId, updateDto, mockUserId)
        ).rejects.toThrow(/No se puede modificar una OT en estado CANCELADA/);
      });

      it('should recalculate totals when tarifa fields change', async () => {
        const mockOrden = createMockOrden({
          estado: EstadoOrdenTransporte.CONFIRMADA,
          tarifaBase: 10000,
          recargos: 0,
          descuentos: 0,
          subtotal: 10000,
          iva: 1600,
          total: 11600,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const updateDto: UpdateOrdenTransporteDto = {
          tarifaBase: 20000,
          recargos: 3000,
          descuentos: 1000,
        };

        const result = await service.update(mockTenantId, mockOrderId, updateDto, mockUserId);

        // New subtotal = 20000 + 3000 - 1000 = 22000
        // New IVA = 22000 * 0.16 = 3520
        // New total = 22000 + 3520 = 25520
        expect(result?.subtotal).toBe(22000);
        expect(result?.iva).toBe(3520);
        expect(result?.total).toBe(25520);
      });

      it('should return null when order not found', async () => {
        mockRepository.findOne.mockResolvedValue(null);

        const result = await service.update(
          mockTenantId,
          'non-existent-id',
          { shipperNombre: 'Test' },
          mockUserId
        );

        expect(result).toBeNull();
      });
    });
  });

  // ===========================================================================
  // BUSINESS LOGIC
  // ===========================================================================

  describe('Business Logic', () => {
    describe('asignarUnidad()', () => {
      const mockAsignarDto: AsignarUnidadDto = {
        unidadId: mockUnidadId,
        operadorId: mockOperadorId,
        remolqueId: '550e8400-e29b-41d4-a716-446655440032',
        fechaSalidaProgramada: new Date('2026-02-10T06:00:00Z'),
        fechaLlegadaProgramada: new Date('2026-02-11T20:00:00Z'),
      };

      it('should assign unit and operator when order is CONFIRMADA', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        // Mock unidad validation - available
        mockManager.query
          .mockResolvedValueOnce([{ id: mockUnidadId, estado: 'DISPONIBLE', activo: true }]) // unidad check
          .mockResolvedValueOnce([]) // no conflicting trips
          .mockResolvedValueOnce([{ // operador check
            id: mockOperadorId,
            activo: true,
            estado: 'DISPONIBLE',
            licencia_vigencia: '2027-01-01',
            certificado_fisico_vigencia: '2027-01-01',
            antidoping_vigencia: '2027-01-01',
          }])
          .mockResolvedValueOnce([{ count: '0' }]) // viaje count
          .mockResolvedValueOnce([{ id: mockViajeId }]); // viaje insert

        const result = await service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.orden.estado).toBe(EstadoOrdenTransporte.ASIGNADA);
        expect(result?.viajeId).toBe(mockViajeId);
      });

      it('should assign unit when order is PENDIENTE', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.PENDIENTE });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        mockManager.query
          .mockResolvedValueOnce([{ id: mockUnidadId, estado: 'DISPONIBLE', activo: true }])
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([{
            id: mockOperadorId,
            activo: true,
            estado: 'DISPONIBLE',
            licencia_vigencia: '2027-01-01',
            certificado_fisico_vigencia: '2027-01-01',
            antidoping_vigencia: '2027-01-01',
          }])
          .mockResolvedValueOnce([{ count: '0' }])
          .mockResolvedValueOnce([{ id: mockViajeId }]);

        const result = await service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.orden.estado).toBe(EstadoOrdenTransporte.ASIGNADA);
      });

      it('should reject assignment when order not found', async () => {
        mockRepository.findOne.mockResolvedValue(null);

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow('Orden de transporte no encontrada');
      });

      it('should reject assignment when order is in invalid state (EN_TRANSITO)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.EN_TRANSITO });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/No se puede asignar unidad a una OT en estado EN_TRANSITO/);
      });

      it('should reject assignment when order already has a viaje assigned', async () => {
        const mockOrden = createMockOrden({
          estado: EstadoOrdenTransporte.CONFIRMADA,
          viajeId: 'existing-viaje-id',
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/La orden ya tiene un viaje asignado/);
      });

      it('should reject assignment to unavailable unit (EN_VIAJE)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query.mockResolvedValueOnce([{
          id: mockUnidadId,
          estado: 'EN_VIAJE',
          activo: true,
        }]);

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/Unidad.*no disponible.*Estado actual: EN_VIAJE/);
      });

      it('should reject assignment to inactive unit', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query.mockResolvedValueOnce([{
          id: mockUnidadId,
          estado: 'DISPONIBLE',
          activo: false,
        }]);

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/Unidad.*no disponible.*Unidad inactiva/);
      });

      it('should reject assignment to non-existent unit', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query.mockResolvedValueOnce([]); // No unit found

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/Unidad.*no disponible.*Unidad no encontrada/);
      });

      it('should reject assignment when unit has conflicting trips', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([{ id: mockUnidadId, estado: 'DISPONIBLE', activo: true }])
          .mockResolvedValueOnce([{ id: 'conflict-viaje', codigo: 'VJ-202602-000001' }]); // Conflicting trip

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/Unidad.*no disponible.*Conflicto con viaje/);
      });

      it('should reject assignment when operator has expired documents', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([{ id: mockUnidadId, estado: 'DISPONIBLE', activo: true }])
          .mockResolvedValueOnce([]) // No conflicting trips
          .mockResolvedValueOnce([{
            id: mockOperadorId,
            activo: true,
            estado: 'DISPONIBLE',
            licencia_vigencia: '2025-01-01', // Expired
            certificado_fisico_vigencia: '2027-01-01',
            antidoping_vigencia: '2027-01-01',
          }]);

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/Operador.*tiene documentos vencidos.*Licencia de conducir/);
      });

      it('should reject assignment when operator not found', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([{ id: mockUnidadId, estado: 'DISPONIBLE', activo: true }])
          .mockResolvedValueOnce([]) // No conflicting trips
          .mockResolvedValueOnce([]); // Operator not found

        await expect(
          service.asignarUnidad(mockOrderId, mockAsignarDto, mockServiceContext)
        ).rejects.toThrow(/documentos vencidos.*Operador no encontrado/);
      });
    });

    describe('calcularTarifa()', () => {
      it('should calculate tarifa using specific tarifa from database', async () => {
        const mockOrden = createMockOrden({
          origenLatitud: 19.3984,
          origenLongitud: -99.1576,
          destinoLatitud: 20.6597,
          destinoLongitud: -103.3496,
          pesoKg: 15000,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        // Mock tarifa lookup
        mockManager.query
          .mockResolvedValueOnce([]) // No lane match
          .mockResolvedValueOnce([{
            id: mockTarifaId,
            codigo: 'TAR-001',
            tipoTarifa: 'FIJO',
            tarifaBase: 20000,
            moneda: 'MXN',
          }]);

        const result = await service.calcularTarifa(mockOrderId, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.tarifaId).toBe(mockTarifaId);
        expect(result?.tarifaBase).toBe(20000);
        expect(result?.moneda).toBe('MXN');
        expect(result?.ivaRate).toBe(0.16);
      });

      it('should calculate tarifa by kilometer when tipoTarifa is POR_KM', async () => {
        const mockOrden = createMockOrden({
          origenLatitud: 19.3984,
          origenLongitud: -99.1576,
          destinoLatitud: 20.6597,
          destinoLongitud: -103.3496,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([]) // No lane
          .mockResolvedValueOnce([{
            id: mockTarifaId,
            codigo: 'TAR-KM',
            tipoTarifa: 'POR_KM',
            tarifaBase: 5000,
            tarifaKm: 25,
            moneda: 'MXN',
          }]);

        const result = await service.calcularTarifa(mockOrderId, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.tarifaBase).toBe(5000);
        expect(result?.montoVariable).toBeGreaterThan(0); // Based on distance
        expect(result?.distanciaEstimadaKm).toBeDefined();
      });

      it('should calculate tarifa by weight when tipoTarifa is POR_TONELADA', async () => {
        const mockOrden = createMockOrden({
          pesoKg: 20000, // 20 tons
          origenLatitud: 19.3984,
          origenLongitud: -99.1576,
          destinoLatitud: 20.6597,
          destinoLongitud: -103.3496,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([{
            id: mockTarifaId,
            codigo: 'TAR-TON',
            tipoTarifa: 'POR_TONELADA',
            tarifaBase: 3000,
            tarifaTonelada: 500,
            moneda: 'MXN',
          }]);

        const result = await service.calcularTarifa(mockOrderId, mockServiceContext);

        expect(result).toBeDefined();
        // montoVariable = 20 tons * 500 = 10000
        expect(result?.montoVariable).toBe(10000);
      });

      it('should use default tarifa when no specific tarifa found', async () => {
        const mockOrden = createMockOrden({
          origenLatitud: 19.3984,
          origenLongitud: -99.1576,
          destinoLatitud: 20.6597,
          destinoLongitud: -103.3496,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([]) // No lane
          .mockResolvedValueOnce([]); // No tarifa

        const result = await service.calcularTarifa(mockOrderId, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.tarifaId).toBeUndefined();
        expect(result?.tarifaBase).toBe(15000); // Default base
        expect(result?.montoVariable).toBeGreaterThan(0); // Default $25/km
      });

      it('should throw error when order not found', async () => {
        mockRepository.findOne.mockResolvedValue(null);

        await expect(
          service.calcularTarifa('non-existent-id', mockServiceContext)
        ).rejects.toThrow('Orden de transporte no encontrada');
      });

      it('should apply minimum tarifa when calculated is below minimum', async () => {
        const mockOrden = createMockOrden({
          pesoKg: 100, // Very light cargo
          origenLatitud: 19.3984,
          origenLongitud: -99.1576,
          destinoLatitud: 19.4000, // Very close
          destinoLongitud: -99.1600,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([{
            id: mockTarifaId,
            codigo: 'TAR-MIN',
            tipoTarifa: 'POR_KM',
            tarifaBase: 1000,
            tarifaKm: 10,
            minimoFacturar: 15000,
            moneda: 'MXN',
          }]);

        const result = await service.calcularTarifa(mockOrderId, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.minimoAplicado).toBe(true);
        expect(result?.tarifaBase).toBe(15000);
      });

      it('should add fuel surcharge for refrigerated cargo', async () => {
        const mockOrden = createMockOrden({
          requiereTemperatura: true,
          origenLatitud: 19.3984,
          origenLongitud: -99.1576,
          destinoLatitud: 20.6597,
          destinoLongitud: -103.3496,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([{
            id: mockTarifaId,
            tipoTarifa: 'FIJO',
            tarifaBase: 20000,
            moneda: 'MXN',
          }]);

        const result = await service.calcularTarifa(mockOrderId, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.recargoCombustible).toBeGreaterThan(0);
      });

      it('should add surcharge for hazardous cargo (PELIGROSA)', async () => {
        const mockOrden = createMockOrden({
          tipoCarga: TipoCarga.PELIGROSA,
          origenLatitud: 19.3984,
          origenLongitud: -99.1576,
          destinoLatitud: 20.6597,
          destinoLongitud: -103.3496,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        mockManager.query
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([{
            id: mockTarifaId,
            tipoTarifa: 'FIJO',
            tarifaBase: 20000,
            moneda: 'MXN',
          }]);

        const result = await service.calcularTarifa(mockOrderId, mockServiceContext);

        expect(result).toBeDefined();
        expect(result?.recargoManiobras).toBeGreaterThanOrEqual(3000); // Fixed surcharge for hazmat
      });
    });

    describe('cambiarEstado()', () => {
      it('should transition from BORRADOR to CONFIRMADA', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.confirm(mockTenantId, mockOrderId, mockUserId);

        expect(result?.estado).toBe(EstadoOrdenTransporte.CONFIRMADA);
        expect(mockRepository.save).toHaveBeenCalled();
      });

      it('should transition from CONFIRMADA to ASIGNADA via assignToViaje', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.assignToViaje(
          mockTenantId,
          mockOrderId,
          mockViajeId,
          mockUserId
        );

        expect(result?.estado).toBe(EstadoOrdenTransporte.ASIGNADA);
        expect(result?.viajeId).toBe(mockViajeId);
      });

      it('should transition from ASIGNADA to EN_PROCESO', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.ASIGNADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.startProcess(mockTenantId, mockOrderId, mockUserId);

        expect(result?.estado).toBe(EstadoOrdenTransporte.EN_PROCESO);
      });

      it('should transition from EN_PROCESO to EN_TRANSITO', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.EN_PROCESO });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.startTransit(mockTenantId, mockOrderId, mockUserId);

        expect(result?.estado).toBe(EstadoOrdenTransporte.EN_TRANSITO);
      });

      it('should transition from EN_TRANSITO to ENTREGADA', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.EN_TRANSITO });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.deliver(mockTenantId, mockOrderId, mockUserId);

        expect(result?.estado).toBe(EstadoOrdenTransporte.ENTREGADA);
      });

      it('should reject invalid state transition (BORRADOR to EN_TRANSITO)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await expect(
          service.startTransit(mockTenantId, mockOrderId, mockUserId)
        ).rejects.toThrow(/Transicion de estado invalida/);
      });

      it('should reject transition from final state (CANCELADA)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CANCELADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await expect(
          service.confirm(mockTenantId, mockOrderId, mockUserId)
        ).rejects.toThrow(/Transicion de estado invalida/);
      });

      it('should reject transition from final state (FACTURADA)', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.FACTURADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await expect(
          service.deliver(mockTenantId, mockOrderId, mockUserId)
        ).rejects.toThrow(/Transicion de estado invalida/);
      });
    });

    describe('cancel()', () => {
      it('should cancel order in BORRADOR state', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.cancel(
          mockTenantId,
          mockOrderId,
          'Cliente solicito cancelacion',
          mockUserId
        );

        expect(result?.estado).toBe(EstadoOrdenTransporte.CANCELADA);
        expect(result?.observaciones).toContain('CANCELADA');
        expect(result?.observaciones).toContain('Cliente solicito cancelacion');
      });

      it('should cancel order in CONFIRMADA state', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.cancel(
          mockTenantId,
          mockOrderId,
          'Error en datos',
          mockUserId
        );

        expect(result?.estado).toBe(EstadoOrdenTransporte.CANCELADA);
      });

      it('should reject cancellation of order EN_TRANSITO', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.EN_TRANSITO });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await expect(
          service.cancel(mockTenantId, mockOrderId, 'Test', mockUserId)
        ).rejects.toThrow(/No se puede cancelar una OT en estado EN_TRANSITO/);
      });

      it('should reject cancellation of already delivered order', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.ENTREGADA });
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await expect(
          service.cancel(mockTenantId, mockOrderId, 'Test', mockUserId)
        ).rejects.toThrow(/No se puede cancelar una OT en estado ENTREGADA/);
      });

      it('should unassign viaje when cancelling assigned order', async () => {
        const mockOrden = createMockOrden({
          estado: EstadoOrdenTransporte.ASIGNADA,
          viajeId: mockViajeId,
        });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const result = await service.cancel(
          mockTenantId,
          mockOrderId,
          'Cambio de planes',
          mockUserId
        );

        expect(result?.estado).toBe(EstadoOrdenTransporte.CANCELADA);
        expect(result?.viajeId).toBeUndefined();
      });
    });
  });

  // ===========================================================================
  // VALIDATION
  // ===========================================================================

  describe('Validation', () => {
    describe('Required fields on create', () => {
      it('should require clienteId', async () => {
        mockRepository.count.mockResolvedValue(0);
        mockRepository.create.mockImplementation((dto: any) => dto as OrdenTransporte);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        const dto = { ...mockCreateDto };
        delete (dto as any).clienteId;

        // The service creates the entity - in a real scenario TypeORM would validate
        // Here we verify the field is passed through
        await service.create(mockTenantId, dto as CreateOrdenTransporteDto, mockUserId);

        expect(mockRepository.create).toHaveBeenCalledWith(
          expect.objectContaining({
            clienteId: undefined,
          })
        );
      });

      it('should require shipperId and shipperNombre', async () => {
        mockRepository.count.mockResolvedValue(0);
        mockRepository.create.mockImplementation((dto: any) => dto as OrdenTransporte);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        await service.create(mockTenantId, mockCreateDto, mockUserId);

        expect(mockRepository.create).toHaveBeenCalledWith(
          expect.objectContaining({
            shipperId: mockCreateDto.shipperId,
            shipperNombre: mockCreateDto.shipperNombre,
          })
        );
      });

      it('should require origen and destino direcciones', async () => {
        mockRepository.count.mockResolvedValue(0);
        mockRepository.create.mockImplementation((dto: any) => dto as OrdenTransporte);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        await service.create(mockTenantId, mockCreateDto, mockUserId);

        expect(mockRepository.create).toHaveBeenCalledWith(
          expect.objectContaining({
            origenDireccion: mockCreateDto.origenDireccion,
            destinoDireccion: mockCreateDto.destinoDireccion,
          })
        );
      });
    });

    describe('State transition validation', () => {
      it('should validate all allowed transitions from BORRADOR', async () => {
        const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR });
        mockRepository.findOne.mockResolvedValue(mockOrden);
        mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

        // Valid transitions from BORRADOR
        const validStates = [
          EstadoOrdenTransporte.PENDIENTE,
          EstadoOrdenTransporte.SOLICITADA,
          EstadoOrdenTransporte.CONFIRMADA,
          EstadoOrdenTransporte.CANCELADA,
        ];

        for (const estado of validStates) {
          mockRepository.findOne.mockResolvedValue(createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR }));
          const result = await service.cambiarEstado(mockTenantId, mockOrderId, estado, mockUserId);
          expect(result?.estado).toBe(estado);
        }
      });
    });

    describe('Tenant isolation', () => {
      it('should always include tenantId in queries', async () => {
        const mockOrden = createMockOrden();
        mockRepository.findOne.mockResolvedValue(mockOrden);

        await service.findById(mockTenantId, mockOrderId);

        expect(mockRepository.findOne).toHaveBeenCalledWith({
          where: expect.objectContaining({
            tenantId: mockTenantId,
          }),
        });
      });

      it('should create orders with correct tenantId', async () => {
        const mockOrden = createMockOrden();
        mockRepository.count.mockResolvedValue(0);
        mockRepository.create.mockReturnValue(mockOrden);
        mockRepository.save.mockResolvedValue(mockOrden);

        await service.create(mockTenantId, mockCreateDto, mockUserId);

        expect(mockRepository.create).toHaveBeenCalledWith(
          expect.objectContaining({
            tenantId: mockTenantId,
          })
        );
      });
    });
  });

  // ===========================================================================
  // ADDITIONAL QUERY METHODS
  // ===========================================================================

  describe('Additional Query Methods', () => {
    describe('findByStatus()', () => {
      it('should return orders by specific status', async () => {
        const mockOrdenes = [
          createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA }),
          createMockOrden({ id: 'order-2', estado: EstadoOrdenTransporte.CONFIRMADA }),
        ];
        mockRepository.find.mockResolvedValue(mockOrdenes);

        const result = await service.findByStatus(
          mockTenantId,
          EstadoOrdenTransporte.CONFIRMADA,
          100
        );

        expect(mockRepository.find).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              estado: EstadoOrdenTransporte.CONFIRMADA,
            }),
          })
        );
        expect(result).toHaveLength(2);
      });
    });

    describe('findUnassigned()', () => {
      it('should return orders without viaje assignment', async () => {
        const mockOrdenes = [createMockOrden({ estado: EstadoOrdenTransporte.CONFIRMADA })];
        mockRepository.find.mockResolvedValue(mockOrdenes);

        const result = await service.findUnassigned(mockTenantId, 100);

        expect(mockRepository.find).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              viajeId: expect.anything(), // IsNull()
            }),
          })
        );
        expect(result).toBeDefined();
      });
    });

    describe('findByViaje()', () => {
      it('should return orders assigned to a specific viaje', async () => {
        const mockOrdenes = [
          createMockOrden({ viajeId: mockViajeId }),
          createMockOrden({ id: 'order-2', viajeId: mockViajeId }),
        ];
        mockRepository.find.mockResolvedValue(mockOrdenes);

        const result = await service.findByViaje(mockTenantId, mockViajeId);

        expect(mockRepository.find).toHaveBeenCalledWith(
          expect.objectContaining({
            where: expect.objectContaining({
              viajeId: mockViajeId,
            }),
          })
        );
        expect(result).toHaveLength(2);
      });
    });

    describe('getStatistics()', () => {
      it('should return statistics for date range', async () => {
        const mockOrdenes = [
          createMockOrden({ estado: EstadoOrdenTransporte.ENTREGADA, total: 30000 }),
          createMockOrden({
            id: 'order-2',
            estado: EstadoOrdenTransporte.CONFIRMADA,
            total: 25000,
          }),
        ];
        mockRepository.find.mockResolvedValue(mockOrdenes);

        const result = await service.getStatistics(mockTenantId, {
          from: new Date('2026-02-01'),
          to: new Date('2026-02-28'),
        });

        expect(result).toBeDefined();
        expect(result.total).toBe(2);
        expect(result.porEstado).toBeDefined();
        expect(result.porModalidad).toBeDefined();
        expect(result.totalIngresos).toBeGreaterThan(0);
      });
    });
  });

  // ===========================================================================
  // BACKWARD COMPATIBILITY ALIASES
  // ===========================================================================

  describe('Backward Compatibility Aliases', () => {
    it('findOne() should call findById()', async () => {
      const mockOrden = createMockOrden();
      mockRepository.findOne.mockResolvedValue(mockOrden);

      const result = await service.findOne(mockTenantId, mockOrderId);

      expect(result).toBeDefined();
      expect(result?.id).toBe(mockOrderId);
    });

    it('confirmar() should call confirm()', async () => {
      const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR });
      mockRepository.findOne.mockResolvedValue(mockOrden);
      mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

      const result = await service.confirmar(mockTenantId, mockOrderId, mockUserId);

      expect(result?.estado).toBe(EstadoOrdenTransporte.CONFIRMADA);
    });

    it('cancelar() should call cancel()', async () => {
      const mockOrden = createMockOrden({ estado: EstadoOrdenTransporte.BORRADOR });
      mockRepository.findOne.mockResolvedValue(mockOrden);
      mockRepository.save.mockImplementation(async (orden: any) => orden as OrdenTransporte);

      const result = await service.cancelar(
        mockTenantId,
        mockOrderId,
        'Motivo test',
        mockUserId
      );

      expect(result?.estado).toBe(EstadoOrdenTransporte.CANCELADA);
    });
  });
});
