import { jest, describe, it, expect, beforeEach } from '@jest/globals';
import { createMockRepository, createMockInvoice } from '../../../__tests__/helpers.js';

// Mock dependencies before importing service
const mockInvoiceRepository = createMockRepository();
const mockPaymentRepository = createMockRepository();

jest.mock('../../../config/typeorm.js', () => ({
  AppDataSource: {
    getRepository: jest.fn((entity: any) => {
      if (entity.name === 'Invoice') return mockInvoiceRepository;
      if (entity.name === 'Payment') return mockPaymentRepository;
      return mockInvoiceRepository;
    }),
  },
}));

// Import after mocking
import { invoicesService } from '../services/index.js';

describe('InvoicesService', () => {
  const tenantId = 'test-tenant-uuid';
  const userId = 'test-user-uuid';

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

  describe('findAllInvoices', () => {
    it('should return invoices with pagination', async () => {
      const mockInvoices = [
        createMockInvoice({ id: '1', invoiceNumber: 'FAC-000001' }),
        createMockInvoice({ id: '2', invoiceNumber: 'FAC-000002' }),
      ];

      mockInvoiceRepository.findAndCount.mockResolvedValue([mockInvoices, 2]);

      const result = await invoicesService.findAllInvoices({ tenantId, limit: 50, offset: 0 });

      expect(result.data).toHaveLength(2);
      expect(result.total).toBe(2);
    });

    it('should filter by invoice type', async () => {
      mockInvoiceRepository.findAndCount.mockResolvedValue([[], 0]);

      await invoicesService.findAllInvoices({ tenantId, invoiceType: 'sale' });

      expect(mockInvoiceRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            invoiceType: 'sale',
          }),
        })
      );
    });

    it('should filter by partner', async () => {
      mockInvoiceRepository.findAndCount.mockResolvedValue([[], 0]);

      await invoicesService.findAllInvoices({ tenantId, partnerId: 'partner-uuid' });

      expect(mockInvoiceRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            partnerId: 'partner-uuid',
          }),
        })
      );
    });

    it('should filter by status', async () => {
      mockInvoiceRepository.findAndCount.mockResolvedValue([[], 0]);

      await invoicesService.findAllInvoices({ tenantId, status: 'validated' });

      expect(mockInvoiceRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            status: 'validated',
          }),
        })
      );
    });
  });

  describe('findInvoice', () => {
    it('should return invoice when found', async () => {
      const mockInvoice = createMockInvoice();
      mockInvoiceRepository.findOne.mockResolvedValue(mockInvoice);

      const result = await invoicesService.findInvoice('invoice-uuid-1', tenantId);

      expect(result).toEqual(mockInvoice);
    });

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

      const result = await invoicesService.findInvoice('nonexistent-id', tenantId);

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

  describe('createInvoice', () => {
    const createDto = {
      invoiceType: 'sale' as const,
      partnerId: 'partner-uuid',
      subtotal: 1000,
      taxAmount: 160,
      totalAmount: 1160,
    };

    it('should create invoice with auto-generated number', async () => {
      mockInvoiceRepository.count.mockResolvedValue(5);
      const savedInvoice = createMockInvoice({ ...createDto, invoiceNumber: 'FAC-000006' });
      mockInvoiceRepository.create.mockReturnValue(savedInvoice);
      mockInvoiceRepository.save.mockResolvedValue(savedInvoice);

      const result = await invoicesService.createInvoice(tenantId, createDto, userId);

      expect(result.invoiceNumber).toBe('FAC-000006');
      expect(mockInvoiceRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          invoiceNumber: 'FAC-000006',
          status: 'draft',
        })
      );
    });

    it('should use correct prefix for purchase invoices', async () => {
      mockInvoiceRepository.count.mockResolvedValue(3);
      const purchaseDto = { ...createDto, invoiceType: 'purchase' as const };
      mockInvoiceRepository.create.mockReturnValue(createMockInvoice({ invoiceNumber: 'FP-000004' }));
      mockInvoiceRepository.save.mockResolvedValue(createMockInvoice({ invoiceNumber: 'FP-000004' }));

      await invoicesService.createInvoice(tenantId, purchaseDto, userId);

      expect(mockInvoiceRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          invoiceNumber: 'FP-000004',
        })
      );
    });

    it('should use correct prefix for credit notes', async () => {
      mockInvoiceRepository.count.mockResolvedValue(1);
      const creditDto = { ...createDto, invoiceType: 'credit_note' as const };
      mockInvoiceRepository.create.mockReturnValue(createMockInvoice({ invoiceNumber: 'NC-000002' }));
      mockInvoiceRepository.save.mockResolvedValue(createMockInvoice({ invoiceNumber: 'NC-000002' }));

      await invoicesService.createInvoice(tenantId, creditDto, userId);

      expect(mockInvoiceRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          invoiceNumber: 'NC-000002',
        })
      );
    });

    it('should set createdBy field', async () => {
      mockInvoiceRepository.count.mockResolvedValue(0);
      mockInvoiceRepository.create.mockReturnValue(createMockInvoice());
      mockInvoiceRepository.save.mockResolvedValue(createMockInvoice());

      await invoicesService.createInvoice(tenantId, createDto, userId);

      expect(mockInvoiceRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          createdBy: userId,
        })
      );
    });
  });

  describe('updateInvoice', () => {
    it('should update invoice successfully', async () => {
      const existingInvoice = createMockInvoice();
      mockInvoiceRepository.findOne.mockResolvedValue(existingInvoice);
      mockInvoiceRepository.save.mockResolvedValue({ ...existingInvoice, notes: 'Updated notes' });

      const result = await invoicesService.updateInvoice(
        'invoice-uuid-1',
        tenantId,
        { notes: 'Updated notes' },
        userId
      );

      expect(result?.notes).toBe('Updated notes');
    });

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

      const result = await invoicesService.updateInvoice(
        'nonexistent-id',
        tenantId,
        { notes: 'Test' },
        userId
      );

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

  describe('deleteInvoice', () => {
    it('should soft delete invoice', async () => {
      mockInvoiceRepository.softDelete.mockResolvedValue({ affected: 1 });

      const result = await invoicesService.deleteInvoice('invoice-uuid-1', tenantId);

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

    it('should return false when invoice not found', async () => {
      mockInvoiceRepository.softDelete.mockResolvedValue({ affected: 0 });

      const result = await invoicesService.deleteInvoice('nonexistent-id', tenantId);

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

  describe('validateInvoice', () => {
    it('should validate draft invoice', async () => {
      const draftInvoice = createMockInvoice({ status: 'draft' });
      mockInvoiceRepository.findOne.mockResolvedValue(draftInvoice);
      mockInvoiceRepository.save.mockResolvedValue({ ...draftInvoice, status: 'validated' });

      const result = await invoicesService.validateInvoice('invoice-uuid-1', tenantId, userId);

      expect(result?.status).toBe('validated');
    });

    it('should return null when invoice not in draft status', async () => {
      const validatedInvoice = createMockInvoice({ status: 'validated' });
      mockInvoiceRepository.findOne.mockResolvedValue(validatedInvoice);

      const result = await invoicesService.validateInvoice('invoice-uuid-1', tenantId, userId);

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

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

      const result = await invoicesService.validateInvoice('nonexistent-id', tenantId, userId);

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

  describe('cancelInvoice', () => {
    it('should cancel invoice', async () => {
      const invoice = createMockInvoice({ status: 'validated' });
      mockInvoiceRepository.findOne.mockResolvedValue(invoice);
      mockInvoiceRepository.save.mockResolvedValue({ ...invoice, status: 'cancelled' });

      const result = await invoicesService.cancelInvoice('invoice-uuid-1', tenantId, userId);

      expect(result?.status).toBe('cancelled');
    });

    it('should return null when invoice already cancelled', async () => {
      const cancelledInvoice = createMockInvoice({ status: 'cancelled' });
      mockInvoiceRepository.findOne.mockResolvedValue(cancelledInvoice);

      const result = await invoicesService.cancelInvoice('invoice-uuid-1', tenantId, userId);

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

  describe('Payment operations', () => {
    const createPaymentDto = {
      partnerId: 'partner-uuid',
      paymentType: 'received' as const,
      paymentMethod: 'transfer',
      amount: 1000,
    };

    it('should find all payments', async () => {
      const mockPayments = [{ id: '1', amount: 1000 }];
      mockPaymentRepository.findAndCount.mockResolvedValue([mockPayments, 1]);

      const result = await invoicesService.findAllPayments(tenantId);

      expect(result.data).toHaveLength(1);
    });

    it('should find all payments filtered by partner', async () => {
      mockPaymentRepository.findAndCount.mockResolvedValue([[], 0]);

      await invoicesService.findAllPayments(tenantId, 'partner-uuid');

      expect(mockPaymentRepository.findAndCount).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({
            partnerId: 'partner-uuid',
          }),
        })
      );
    });

    it('should create payment with auto-generated number', async () => {
      mockPaymentRepository.count.mockResolvedValue(10);
      const savedPayment = { id: '1', paymentNumber: 'PAG-000011', ...createPaymentDto };
      mockPaymentRepository.create.mockReturnValue(savedPayment);
      mockPaymentRepository.save.mockResolvedValue(savedPayment);

      const result = await invoicesService.createPayment(tenantId, createPaymentDto, userId);

      expect(result.paymentNumber).toBe('PAG-000011');
    });

    it('should confirm payment', async () => {
      const draftPayment = { id: '1', status: 'draft' };
      mockPaymentRepository.findOne.mockResolvedValue(draftPayment);
      mockPaymentRepository.save.mockResolvedValue({ ...draftPayment, status: 'confirmed' });

      const result = await invoicesService.confirmPayment('payment-uuid', tenantId, userId);

      expect(result?.status).toBe('confirmed');
    });

    it('should not confirm already confirmed payment', async () => {
      const confirmedPayment = { id: '1', status: 'confirmed' };
      mockPaymentRepository.findOne.mockResolvedValue(confirmedPayment);

      const result = await invoicesService.confirmPayment('payment-uuid', tenantId, userId);

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

    it('should cancel payment', async () => {
      const payment = { id: '1', status: 'confirmed' };
      mockPaymentRepository.findOne.mockResolvedValue(payment);
      mockPaymentRepository.save.mockResolvedValue({ ...payment, status: 'cancelled' });

      const result = await invoicesService.cancelPayment('payment-uuid', tenantId, userId);

      expect(result?.status).toBe('cancelled');
    });
  });
});
