/**
 * @fileoverview Unit tests for CartaPorteService
 * Tests cover CRUD operations, validation, timbrado, cancelacion, and error handling
 * Following pattern from financial/__tests__/accounts.service.spec.ts
 */

import { Repository } from 'typeorm';
import {
  CartaPorte,
  EstadoCartaPorte,
  TipoCfdiCartaPorte,
  UbicacionCartaPorte,
  MercanciaCartaPorte,
  FiguraTransporte,
  AutotransporteCartaPorte,
} from '../entities';
import {
  CartaPorteService,
  CartaPorteSearchParams,
  CreateCartaPorteDto,
  UpdateCartaPorteDto,
  TimbrarCartaPorteDto,
  CancelarCartaPorteDto,
} from '../services/carta-porte.service';

describe('CartaPorteService', () => {
  let service: CartaPorteService;
  let mockCartaPorteRepository: Partial<Repository<CartaPorte>>;
  let mockUbicacionRepository: Partial<Repository<UbicacionCartaPorte>>;
  let mockMercanciaRepository: Partial<Repository<MercanciaCartaPorte>>;
  let mockFiguraRepository: Partial<Repository<FiguraTransporte>>;
  let mockAutotransporteRepository: Partial<Repository<AutotransporteCartaPorte>>;

  const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
  const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
  const mockViajeId = '550e8400-e29b-41d4-a716-446655440003';
  const mockCartaPorteId = '550e8400-e29b-41d4-a716-446655440010';

  // Mock Ubicacion
  const mockUbicacion: Partial<UbicacionCartaPorte> = {
    id: '550e8400-e29b-41d4-a716-446655440020',
    tenantId: mockTenantId,
    cartaPorteId: mockCartaPorteId,
    tipoUbicacion: 'Origen',
    codigoPostal: '06600',
    secuencia: 1,
  };

  const mockUbicacionDestino: Partial<UbicacionCartaPorte> = {
    id: '550e8400-e29b-41d4-a716-446655440021',
    tenantId: mockTenantId,
    cartaPorteId: mockCartaPorteId,
    tipoUbicacion: 'Destino',
    codigoPostal: '44100',
    secuencia: 2,
  };

  // Mock Mercancia
  const mockMercancia: Partial<MercanciaCartaPorte> = {
    id: '550e8400-e29b-41d4-a716-446655440030',
    tenantId: mockTenantId,
    cartaPorteId: mockCartaPorteId,
    bienesTransp: '31161700',
    descripcion: 'Materiales de construccion',
    cantidad: 100,
    claveUnidad: 'KGM',
    pesoEnKg: 1000,
    secuencia: 1,
  };

  // Mock Figura Transporte
  const mockFigura: Partial<FiguraTransporte> = {
    id: '550e8400-e29b-41d4-a716-446655440040',
    tenantId: mockTenantId,
    cartaPorteId: mockCartaPorteId,
    tipoFigura: '01',
    rfcFigura: 'XAXX010101000',
    nombreFigura: 'Juan Perez',
    numLicencia: 'ABC123456',
  };

  // Mock Autotransporte
  const mockAutotransporte: Partial<AutotransporteCartaPorte> = {
    id: '550e8400-e29b-41d4-a716-446655440050',
    tenantId: mockTenantId,
    cartaPorteId: mockCartaPorteId,
    permSct: 'TPAF01',
    numPermisoSct: 'PERM123456',
    configVehicular: 'C2',
    placaVm: 'ABC1234',
    anioModeloVm: 2022,
  };

  // Mock CartaPorte completa
  const mockCartaPorte: Partial<CartaPorte> = {
    id: mockCartaPorteId,
    tenantId: mockTenantId,
    viajeId: mockViajeId,
    tipoCfdi: TipoCfdiCartaPorte.INGRESO,
    versionCartaPorte: '3.1',
    serie: 'CP',
    folio: '001',
    emisorRfc: 'XAXX010101000',
    emisorNombre: 'Transportes SA de CV',
    emisorRegimenFiscal: '601',
    receptorRfc: 'XBXX010101000',
    receptorNombre: 'Cliente SA de CV',
    receptorUsoCfdi: 'G03',
    receptorDomicilioFiscalCp: '06600',
    subtotal: 10000,
    total: 11600,
    moneda: 'MXN',
    transporteInternacional: false,
    permisoSct: 'TPAF01',
    numPermisoSct: 'PERM123456',
    configVehicular: 'C2',
    pesoBrutoTotal: 1000,
    unidadPeso: 'KGM',
    numTotalMercancias: 1,
    estado: EstadoCartaPorte.BORRADOR,
    ubicaciones: [mockUbicacion as UbicacionCartaPorte, mockUbicacionDestino as UbicacionCartaPorte],
    mercancias: [mockMercancia as MercanciaCartaPorte],
    figuras: [mockFigura as FiguraTransporte],
    autotransporte: [mockAutotransporte as AutotransporteCartaPorte],
    createdAt: new Date('2026-01-01'),
    updatedAt: new Date('2026-01-01'),
    createdById: mockUserId,
  };

  // Mock CartaPorte validada
  const mockCartaPorteValidada: Partial<CartaPorte> = {
    ...mockCartaPorte,
    estado: EstadoCartaPorte.VALIDADA,
  };

  // Mock CartaPorte timbrada
  const mockCartaPorteTimbrada: Partial<CartaPorte> = {
    ...mockCartaPorte,
    estado: EstadoCartaPorte.TIMBRADA,
    uuidCfdi: '550e8400-e29b-41d4-a716-446655440099',
    fechaTimbrado: new Date('2026-01-02'),
    xmlCfdi: '<cfdi:Comprobante ...>',
    xmlCartaPorte: '<cartaporte31:CartaPorte ...>',
  };

  // Mock CartaPorte cancelada
  const mockCartaPorteCancelada: Partial<CartaPorte> = {
    ...mockCartaPorteTimbrada,
    estado: EstadoCartaPorte.CANCELADA,
    fechaCancelacion: new Date('2026-01-03'),
    motivoCancelacion: 'Error en datos',
  };

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

    // Setup mock repositories
    mockCartaPorteRepository = {
      create: jest.fn().mockReturnValue(mockCartaPorte),
      save: jest.fn().mockResolvedValue(mockCartaPorte),
      findOne: jest.fn().mockResolvedValue(mockCartaPorte),
      find: jest.fn().mockResolvedValue([mockCartaPorte]),
      findAndCount: jest.fn().mockResolvedValue([[mockCartaPorte], 1]),
      delete: jest.fn().mockResolvedValue({ affected: 1 }),
    };

    mockUbicacionRepository = {
      create: jest.fn().mockReturnValue(mockUbicacion),
      save: jest.fn().mockResolvedValue(mockUbicacion),
      findOne: jest.fn().mockResolvedValue(mockUbicacion),
    };

    mockMercanciaRepository = {
      create: jest.fn().mockReturnValue(mockMercancia),
      save: jest.fn().mockResolvedValue(mockMercancia),
      findOne: jest.fn().mockResolvedValue(mockMercancia),
    };

    mockFiguraRepository = {
      create: jest.fn().mockReturnValue(mockFigura),
      save: jest.fn().mockResolvedValue(mockFigura),
      findOne: jest.fn().mockResolvedValue(mockFigura),
    };

    mockAutotransporteRepository = {
      create: jest.fn().mockReturnValue(mockAutotransporte),
      save: jest.fn().mockResolvedValue(mockAutotransporte),
      findOne: jest.fn().mockResolvedValue(mockAutotransporte),
    };

    // Create service instance with mocked repositories
    service = new CartaPorteService(
      mockCartaPorteRepository as Repository<CartaPorte>,
      mockUbicacionRepository as Repository<UbicacionCartaPorte>,
      mockMercanciaRepository as Repository<MercanciaCartaPorte>,
      mockFiguraRepository as Repository<FiguraTransporte>,
      mockAutotransporteRepository as Repository<AutotransporteCartaPorte>,
    );
  });

  describe('findAll', () => {
    it('should return paginated cartas porte with default params', async () => {
      const params: CartaPorteSearchParams = { tenantId: mockTenantId };

      const result = await service.findAll(params);

      expect(mockCartaPorteRepository.findAndCount).toHaveBeenCalled();
      expect(result.data).toEqual([mockCartaPorte]);
      expect(result.total).toBe(1);
    });

    it('should filter by estado', async () => {
      const params: CartaPorteSearchParams = {
        tenantId: mockTenantId,
        estado: EstadoCartaPorte.BORRADOR,
      };

      await service.findAll(params);

      expect(mockCartaPorteRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.arrayContaining([
            expect.objectContaining({ estado: EstadoCartaPorte.BORRADOR }),
          ]),
        })
      );
    });

    it('should filter by tipoCfdi', async () => {
      const params: CartaPorteSearchParams = {
        tenantId: mockTenantId,
        tipoCfdi: TipoCfdiCartaPorte.INGRESO,
      };

      await service.findAll(params);

      expect(mockCartaPorteRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.arrayContaining([
            expect.objectContaining({ tipoCfdi: TipoCfdiCartaPorte.INGRESO }),
          ]),
        })
      );
    });

    it('should filter by viajeId', async () => {
      const params: CartaPorteSearchParams = {
        tenantId: mockTenantId,
        viajeId: mockViajeId,
      };

      await service.findAll(params);

      expect(mockCartaPorteRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.arrayContaining([
            expect.objectContaining({ viajeId: mockViajeId }),
          ]),
        })
      );
    });

    it('should filter by search term across multiple fields', async () => {
      const params: CartaPorteSearchParams = {
        tenantId: mockTenantId,
        search: 'Transportes',
      };

      await service.findAll(params);

      expect(mockCartaPorteRepository.findAndCount).toHaveBeenCalled();
      // Verify multiple where clauses are generated for search
      const callArgs = (mockCartaPorteRepository.findAndCount as jest.Mock).mock.calls[0][0];
      expect(callArgs.where.length).toBeGreaterThan(1);
    });

    it('should filter by date range', async () => {
      const fechaDesde = new Date('2026-01-01');
      const fechaHasta = new Date('2026-01-31');
      const params: CartaPorteSearchParams = {
        tenantId: mockTenantId,
        fechaDesde,
        fechaHasta,
      };

      await service.findAll(params);

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

    it('should apply pagination with limit and offset', async () => {
      const params: CartaPorteSearchParams = {
        tenantId: mockTenantId,
        limit: 10,
        offset: 20,
      };

      await service.findAll(params);

      expect(mockCartaPorteRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          take: 10,
          skip: 20,
        })
      );
    });

    it('should include relations in query', async () => {
      const params: CartaPorteSearchParams = { tenantId: mockTenantId };

      await service.findAll(params);

      expect(mockCartaPorteRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
        })
      );
    });
  });

  describe('findOne', () => {
    it('should return carta porte by id and tenantId', async () => {
      const result = await service.findOne(mockCartaPorteId, mockTenantId);

      expect(mockCartaPorteRepository.findOne).toHaveBeenCalledWith({
        where: { id: mockCartaPorteId, tenantId: mockTenantId },
        relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
      });
      expect(result).toEqual(mockCartaPorte);
    });

    it('should return null when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

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

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

    it('should enforce tenant isolation', async () => {
      const differentTenantId = '550e8400-e29b-41d4-a716-446655440999';
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      const result = await service.findOne(mockCartaPorteId, differentTenantId);

      expect(mockCartaPorteRepository.findOne).toHaveBeenCalledWith({
        where: { id: mockCartaPorteId, tenantId: differentTenantId },
        relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
      });
      expect(result).toBeNull();
    });
  });

  describe('findByViaje', () => {
    it('should return cartas porte for a viaje', async () => {
      const result = await service.findByViaje(mockViajeId, mockTenantId);

      expect(mockCartaPorteRepository.find).toHaveBeenCalledWith({
        where: { viajeId: mockViajeId, tenantId: mockTenantId },
        relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
        order: { createdAt: 'DESC' },
      });
      expect(result).toEqual([mockCartaPorte]);
    });
  });

  describe('findByUuid', () => {
    it('should return carta porte by UUID CFDI', async () => {
      const uuidCfdi = '550e8400-e29b-41d4-a716-446655440099';

      const result = await service.findByUuid(uuidCfdi, mockTenantId);

      expect(mockCartaPorteRepository.findOne).toHaveBeenCalledWith({
        where: { uuidCfdi, tenantId: mockTenantId },
        relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
      });
    });
  });

  describe('create', () => {
    it('should create a new carta porte in BORRADOR state', async () => {
      const createDto: CreateCartaPorteDto = {
        viajeId: mockViajeId,
        tipoCfdi: TipoCfdiCartaPorte.INGRESO,
        emisorRfc: 'XAXX010101000',
        emisorNombre: 'Transportes SA de CV',
        receptorRfc: 'XBXX010101000',
        receptorNombre: 'Cliente SA de CV',
      };

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

      expect(mockCartaPorteRepository.create).toHaveBeenCalledWith({
        ...createDto,
        tenantId: mockTenantId,
        estado: EstadoCartaPorte.BORRADOR,
        createdById: mockUserId,
      });
      expect(mockCartaPorteRepository.save).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    it('should set default version to 3.1', async () => {
      const createDto: CreateCartaPorteDto = {
        viajeId: mockViajeId,
        tipoCfdi: TipoCfdiCartaPorte.TRASLADO,
        emisorRfc: 'XAXX010101000',
        emisorNombre: 'Transportes SA de CV',
        receptorRfc: 'XBXX010101000',
        receptorNombre: 'Cliente SA de CV',
      };

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

      expect(mockCartaPorteRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          estado: EstadoCartaPorte.BORRADOR,
        })
      );
    });
  });

  describe('update', () => {
    it('should update carta porte in BORRADOR state', async () => {
      const updateDto: UpdateCartaPorteDto = {
        emisorNombre: 'Updated Name',
        total: 15000,
      };

      mockCartaPorteRepository.save = jest.fn().mockResolvedValue({
        ...mockCartaPorte,
        ...updateDto,
      });

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

      expect(mockCartaPorteRepository.findOne).toHaveBeenCalled();
      expect(mockCartaPorteRepository.save).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    it('should return null when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      const updateDto: UpdateCartaPorteDto = { emisorNombre: 'Updated' };
      const result = await service.update('non-existent-id', mockTenantId, updateDto, mockUserId);

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

    it('should throw error when updating TIMBRADA carta porte', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteTimbrada);

      const updateDto: UpdateCartaPorteDto = { emisorNombre: 'Updated' };

      await expect(
        service.update(mockCartaPorteId, mockTenantId, updateDto, mockUserId)
      ).rejects.toThrow('No se puede modificar una Carta Porte ya timbrada');
    });

    it('should throw error when updating CANCELADA carta porte', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteCancelada);

      const updateDto: UpdateCartaPorteDto = { emisorNombre: 'Updated' };

      await expect(
        service.update(mockCartaPorteId, mockTenantId, updateDto, mockUserId)
      ).rejects.toThrow('No se puede modificar una Carta Porte cancelada');
    });
  });

  describe('validar', () => {
    it('should return valid=true when all required fields are present', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should return valid=false when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

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

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Carta Porte no encontrada');
    });

    it('should return errors when emisor RFC is missing', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, emisorRfc: null };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('RFC del emisor es requerido');
    });

    it('should return errors when emisor nombre is missing', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, emisorNombre: null };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Nombre del emisor es requerido');
    });

    it('should return errors when receptor RFC is missing', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, receptorRfc: null };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('RFC del receptor es requerido');
    });

    it('should return errors when less than 2 ubicaciones', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, ubicaciones: [mockUbicacion] };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Se requieren al menos 2 ubicaciones (origen y destino)');
    });

    it('should return errors when no mercancias', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, mercancias: [] };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Se requiere al menos una mercancia');
    });

    it('should return errors when no figuras de transporte', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, figuras: [] };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Se requiere al menos una figura de transporte (operador)');
    });

    it('should return errors when no autotransporte', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, autotransporte: [] };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Se requiere informacion de autotransporte');
    });

    it('should return errors when permisoSct is missing', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, permisoSct: null };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Tipo de permiso SCT es requerido');
    });

    it('should return errors when configVehicular is missing', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, configVehicular: null };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Configuracion vehicular es requerida');
    });

    it('should require total for CFDI de INGRESO', async () => {
      const ingresoSinTotal = {
        ...mockCartaPorte,
        tipoCfdi: TipoCfdiCartaPorte.INGRESO,
        total: null,
      };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(ingresoSinTotal);

      const result = await service.validar(mockCartaPorteId, mockTenantId);

      expect(result.valid).toBe(false);
      expect(result.errors).toContain('Total del CFDI es requerido para tipo Ingreso');
    });
  });

  describe('marcarValidada', () => {
    it('should mark carta porte as VALIDADA when validation passes', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);
      mockCartaPorteRepository.save = jest.fn().mockResolvedValue({
        ...mockCartaPorte,
        estado: EstadoCartaPorte.VALIDADA,
      });

      const result = await service.marcarValidada(mockCartaPorteId, mockTenantId);

      expect(result).toBeDefined();
      expect(mockCartaPorteRepository.save).toHaveBeenCalled();
    });

    it('should return null when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

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

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

    it('should throw error when validation fails', async () => {
      const incompleteCartaPorte = { ...mockCartaPorte, emisorRfc: null };
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(incompleteCartaPorte);

      await expect(
        service.marcarValidada(mockCartaPorteId, mockTenantId)
      ).rejects.toThrow('Carta Porte no valida');
    });
  });

  describe('timbrar', () => {
    it('should timbrar carta porte in VALIDADA state', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteValidada);

      const timbrarDto: TimbrarCartaPorteDto = {
        uuidCfdi: '550e8400-e29b-41d4-a716-446655440099',
        fechaTimbrado: new Date('2026-01-02'),
        xmlCfdi: '<cfdi:Comprobante ...>',
        xmlCartaPorte: '<cartaporte31:CartaPorte ...>',
        pdfUrl: 'https://example.com/pdf/carta-porte.pdf',
        qrUrl: 'https://example.com/qr/carta-porte.png',
      };

      mockCartaPorteRepository.save = jest.fn().mockResolvedValue({
        ...mockCartaPorteValidada,
        ...timbrarDto,
        estado: EstadoCartaPorte.TIMBRADA,
      });

      const result = await service.timbrar(mockCartaPorteId, mockTenantId, timbrarDto);

      expect(result).toBeDefined();
      expect(mockCartaPorteRepository.save).toHaveBeenCalled();
    });

    it('should return null when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      const timbrarDto: TimbrarCartaPorteDto = {
        uuidCfdi: '550e8400-e29b-41d4-a716-446655440099',
        fechaTimbrado: new Date(),
        xmlCfdi: '<cfdi>',
        xmlCartaPorte: '<cartaporte>',
      };

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

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

    it('should throw error when carta porte is not VALIDADA', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte); // BORRADOR

      const timbrarDto: TimbrarCartaPorteDto = {
        uuidCfdi: '550e8400-e29b-41d4-a716-446655440099',
        fechaTimbrado: new Date(),
        xmlCfdi: '<cfdi>',
        xmlCartaPorte: '<cartaporte>',
      };

      await expect(
        service.timbrar(mockCartaPorteId, mockTenantId, timbrarDto)
      ).rejects.toThrow('La Carta Porte debe estar validada antes de timbrar');
    });
  });

  describe('cancelar', () => {
    it('should cancel carta porte in TIMBRADA state', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteTimbrada);

      const cancelarDto: CancelarCartaPorteDto = {
        motivoCancelacion: 'Error en datos del receptor',
        uuidSustitucion: '550e8400-e29b-41d4-a716-446655440100',
      };

      mockCartaPorteRepository.save = jest.fn().mockResolvedValue({
        ...mockCartaPorteTimbrada,
        estado: EstadoCartaPorte.CANCELADA,
        fechaCancelacion: expect.any(Date),
        motivoCancelacion: cancelarDto.motivoCancelacion,
        uuidSustitucion: cancelarDto.uuidSustitucion,
      });

      const result = await service.cancelar(mockCartaPorteId, mockTenantId, cancelarDto);

      expect(result).toBeDefined();
      expect(mockCartaPorteRepository.save).toHaveBeenCalled();
    });

    it('should return null when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      const cancelarDto: CancelarCartaPorteDto = {
        motivoCancelacion: 'Error',
      };

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

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

    it('should throw error when carta porte is not TIMBRADA', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte); // BORRADOR

      const cancelarDto: CancelarCartaPorteDto = {
        motivoCancelacion: 'Error',
      };

      await expect(
        service.cancelar(mockCartaPorteId, mockTenantId, cancelarDto)
      ).rejects.toThrow('Solo se pueden cancelar Cartas Porte timbradas');
    });
  });

  describe('addUbicacion', () => {
    it('should add ubicacion to carta porte in BORRADOR state', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);

      const ubicacionData: Partial<UbicacionCartaPorte> = {
        tipoUbicacion: 'Origen',
        codigoPostal: '06600',
        secuencia: 1,
      };

      const result = await service.addUbicacion(mockCartaPorteId, mockTenantId, ubicacionData);

      expect(mockUbicacionRepository.create).toHaveBeenCalledWith({
        ...ubicacionData,
        cartaPorteId: mockCartaPorteId,
      });
      expect(mockUbicacionRepository.save).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    it('should throw error when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      await expect(
        service.addUbicacion(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Carta Porte no encontrada');
    });

    it('should throw error when carta porte is not BORRADOR', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteValidada);

      await expect(
        service.addUbicacion(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Solo se pueden agregar ubicaciones en estado BORRADOR');
    });
  });

  describe('addMercancia', () => {
    it('should add mercancia to carta porte in BORRADOR state', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);

      const mercanciaData: Partial<MercanciaCartaPorte> = {
        bienesTransp: '31161700',
        descripcion: 'Test mercancia',
        cantidad: 10,
        claveUnidad: 'KGM',
        pesoEnKg: 100,
        secuencia: 2,
      };

      const result = await service.addMercancia(mockCartaPorteId, mockTenantId, mercanciaData);

      expect(mockMercanciaRepository.create).toHaveBeenCalledWith({
        ...mercanciaData,
        cartaPorteId: mockCartaPorteId,
      });
      expect(mockMercanciaRepository.save).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    it('should throw error when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      await expect(
        service.addMercancia(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Carta Porte no encontrada');
    });

    it('should throw error when carta porte is not BORRADOR', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteTimbrada);

      await expect(
        service.addMercancia(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Solo se pueden agregar mercancias en estado BORRADOR');
    });
  });

  describe('addFigura', () => {
    it('should add figura to carta porte in BORRADOR state', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);

      const figuraData: Partial<FiguraTransporte> = {
        tipoFigura: '01',
        rfcFigura: 'XAXX010101000',
        nombreFigura: 'Operador Test',
        numLicencia: 'LIC123456',
      };

      const result = await service.addFigura(mockCartaPorteId, mockTenantId, figuraData);

      expect(mockFiguraRepository.create).toHaveBeenCalledWith({
        ...figuraData,
        cartaPorteId: mockCartaPorteId,
      });
      expect(mockFiguraRepository.save).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    it('should throw error when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      await expect(
        service.addFigura(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Carta Porte no encontrada');
    });

    it('should throw error when carta porte is not BORRADOR', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteCancelada);

      await expect(
        service.addFigura(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Solo se pueden agregar figuras en estado BORRADOR');
    });
  });

  describe('addAutotransporte', () => {
    it('should add autotransporte to carta porte in BORRADOR state', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);

      const autoData: Partial<AutotransporteCartaPorte> = {
        permSct: 'TPAF01',
        numPermisoSct: 'PERM999',
        configVehicular: 'C3',
        placaVm: 'XYZ9999',
        anioModeloVm: 2024,
      };

      const result = await service.addAutotransporte(mockCartaPorteId, mockTenantId, autoData);

      expect(mockAutotransporteRepository.create).toHaveBeenCalledWith({
        ...autoData,
        cartaPorteId: mockCartaPorteId,
      });
      expect(mockAutotransporteRepository.save).toHaveBeenCalled();
      expect(result).toBeDefined();
    });

    it('should throw error when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      await expect(
        service.addAutotransporte(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Carta Porte no encontrada');
    });

    it('should throw error when carta porte is not BORRADOR', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteValidada);

      await expect(
        service.addAutotransporte(mockCartaPorteId, mockTenantId, {})
      ).rejects.toThrow('Solo se puede agregar autotransporte en estado BORRADOR');
    });
  });

  describe('getPendientesTimbrar', () => {
    it('should return cartas porte in BORRADOR or VALIDADA state', async () => {
      mockCartaPorteRepository.find = jest.fn().mockResolvedValue([
        mockCartaPorte,
        mockCartaPorteValidada,
      ]);

      const result = await service.getPendientesTimbrar(mockTenantId);

      expect(mockCartaPorteRepository.find).toHaveBeenCalledWith({
        where: {
          tenantId: mockTenantId,
          estado: expect.anything(), // In([...])
        },
        order: { createdAt: 'ASC' },
      });
      expect(result).toHaveLength(2);
    });
  });

  describe('getCartasPorteViaje', () => {
    it('should return all cartas porte for a specific viaje', async () => {
      const result = await service.getCartasPorteViaje(mockViajeId, mockTenantId);

      expect(mockCartaPorteRepository.find).toHaveBeenCalledWith({
        where: { viajeId: mockViajeId, tenantId: mockTenantId },
        relations: ['ubicaciones', 'mercancias', 'figuras', 'autotransporte'],
        order: { createdAt: 'DESC' },
      });
      expect(result).toEqual([mockCartaPorte]);
    });
  });

  describe('delete', () => {
    it('should delete carta porte in BORRADOR state', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);
      mockCartaPorteRepository.delete = jest.fn().mockResolvedValue({ affected: 1 });

      const result = await service.delete(mockCartaPorteId, mockTenantId);

      expect(mockCartaPorteRepository.findOne).toHaveBeenCalled();
      expect(mockCartaPorteRepository.delete).toHaveBeenCalledWith(mockCartaPorteId);
      expect(result).toBe(true);
    });

    it('should return false when carta porte not found', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

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

      expect(result).toBe(false);
    });

    it('should throw error when deleting VALIDADA carta porte', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteValidada);

      await expect(
        service.delete(mockCartaPorteId, mockTenantId)
      ).rejects.toThrow('Solo se pueden eliminar Cartas Porte en estado BORRADOR');
    });

    it('should throw error when deleting TIMBRADA carta porte', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteTimbrada);

      await expect(
        service.delete(mockCartaPorteId, mockTenantId)
      ).rejects.toThrow('Solo se pueden eliminar Cartas Porte en estado BORRADOR');
    });

    it('should throw error when deleting CANCELADA carta porte', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorteCancelada);

      await expect(
        service.delete(mockCartaPorteId, mockTenantId)
      ).rejects.toThrow('Solo se pueden eliminar Cartas Porte en estado BORRADOR');
    });

    it('should return false when delete affects 0 rows', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(mockCartaPorte);
      mockCartaPorteRepository.delete = jest.fn().mockResolvedValue({ affected: 0 });

      const result = await service.delete(mockCartaPorteId, mockTenantId);

      expect(result).toBe(false);
    });
  });

  describe('Tenant Isolation', () => {
    const differentTenantId = '550e8400-e29b-41d4-a716-446655440888';

    it('should not return carta porte from different tenant in findOne', async () => {
      mockCartaPorteRepository.findOne = jest.fn().mockResolvedValue(null);

      const result = await service.findOne(mockCartaPorteId, differentTenantId);

      expect(mockCartaPorteRepository.findOne).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({ tenantId: differentTenantId }),
        })
      );
      expect(result).toBeNull();
    });

    it('should filter by tenantId in findAll', async () => {
      await service.findAll({ tenantId: differentTenantId });

      const callArgs = (mockCartaPorteRepository.findAndCount as jest.Mock).mock.calls[0][0];
      expect(callArgs.where[0].tenantId).toBe(differentTenantId);
    });

    it('should include tenantId when creating carta porte', async () => {
      const createDto: CreateCartaPorteDto = {
        viajeId: mockViajeId,
        tipoCfdi: TipoCfdiCartaPorte.INGRESO,
        emisorRfc: 'XAXX010101000',
        emisorNombre: 'Transportes SA de CV',
        receptorRfc: 'XBXX010101000',
        receptorNombre: 'Cliente SA de CV',
      };

      await service.create(differentTenantId, createDto, mockUserId);

      expect(mockCartaPorteRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({ tenantId: differentTenantId })
      );
    });
  });
});
