/**
 * @alga-psa/billing - Invoice Model Tests
 *
 * Tests for the Invoice model business logic.
 * These tests verify validation logic and error handling.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import Invoice from '../src/models/invoice';

// Mock Knex to test validation logic without database
const createMockKnex = () => {
  const mockInsert = vi.fn().mockReturnThis();
  const mockWhere = vi.fn().mockReturnThis();
  const mockFirst = vi.fn();
  const mockUpdate = vi.fn().mockReturnThis();
  const mockDel = vi.fn();
  const mockReturning = vi.fn();
  const mockSelect = vi.fn().mockReturnThis();
  const mockLeftJoin = vi.fn().mockReturnThis();
  const mockAndOn = vi.fn().mockReturnThis();
  const mockOn = vi.fn().mockReturnThis();
  const mockRaw = vi.fn((sql: string) => sql);
  const mockOrderBy = vi.fn().mockReturnThis();

  const mockKnex = vi.fn(() => ({
    insert: mockInsert,
    where: mockWhere,
    first: mockFirst,
    update: mockUpdate,
    del: mockDel,
    returning: mockReturning,
    select: mockSelect,
    leftJoin: mockLeftJoin,
    andOn: mockAndOn,
    on: mockOn,
    orderBy: mockOrderBy,
  }));

  // Add raw method
  (mockKnex as any).raw = mockRaw;

  // Add schema for hasTable
  (mockKnex as any).schema = {
    hasTable: vi.fn().mockResolvedValue(false),
  };

  return {
    knex: mockKnex as any,
    mocks: {
      insert: mockInsert,
      where: mockWhere,
      first: mockFirst,
      update: mockUpdate,
      del: mockDel,
      returning: mockReturning,
      select: mockSelect,
      raw: mockRaw,
    },
  };
};

describe('Invoice Model', () => {
  describe('create', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();
      const invoice = {
        client_id: 'client-123',
        invoice_date: new Date(),
        due_date: new Date(),
        subtotal: 10000,
        tax: 1000,
        total_amount: 11000,
        currency_code: 'USD',
        status: 'draft' as const,
        invoice_number: 'INV-001',
        credit_applied: 0,
        is_manual: false,
        invoice_charges: [],
      };

      await expect(Invoice.create(knex, '', invoice)).rejects.toThrow(
        'Tenant context is required for creating invoice'
      );
    });

    it('should throw error when total_amount is not an integer', async () => {
      const { knex } = createMockKnex();
      const invoice = {
        client_id: 'client-123',
        invoice_date: new Date(),
        due_date: new Date(),
        subtotal: 10000,
        tax: 1000,
        total_amount: 11000.5, // Not an integer
        currency_code: 'USD',
        status: 'draft' as const,
        invoice_number: 'INV-001',
        credit_applied: 0,
        is_manual: false,
        invoice_charges: [],
      };

      await expect(Invoice.create(knex, 'tenant-123', invoice)).rejects.toThrow(
        'Total amount must be an integer'
      );
    });
  });

  describe('getById', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.getById(knex, '', 'invoice-123')).rejects.toThrow(
        'Tenant context is required for getting invoice'
      );
    });
  });

  describe('update', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.update(knex, '', 'invoice-123', {})).rejects.toThrow(
        'Tenant context is required for updating invoice'
      );
    });
  });

  describe('delete', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.delete(knex, '', 'invoice-123')).rejects.toThrow(
        'Tenant context is required for deleting invoice'
      );
    });
  });

  describe('getAll', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.getAll(knex, '')).rejects.toThrow(
        'Tenant context is required for listing invoices'
      );
    });
  });

  describe('addInvoiceCharge', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();
      const charge = {
        invoice_id: 'invoice-123',
        description: 'Test charge',
        quantity: 1,
        unit_price: 10000,
        total_price: 10000,
        tax_amount: 1000,
        net_amount: 9000,
        rate: 10000,
        is_manual: true,
      };

      await expect(Invoice.addInvoiceCharge(knex, '', charge)).rejects.toThrow(
        'Tenant context is required for adding invoice charge'
      );
    });

    it('should throw error when total_price is not an integer', async () => {
      const { knex } = createMockKnex();
      const charge = {
        invoice_id: 'invoice-123',
        description: 'Test charge',
        quantity: 1,
        unit_price: 10000,
        total_price: 10000.5, // Not an integer
        tax_amount: 1000,
        net_amount: 9000,
        rate: 10000,
        is_manual: true,
      };

      await expect(Invoice.addInvoiceCharge(knex, 'tenant-123', charge)).rejects.toThrow(
        'Total price must be an integer'
      );
    });

    it('should throw error when unit_price is not an integer', async () => {
      const { knex } = createMockKnex();
      const charge = {
        invoice_id: 'invoice-123',
        description: 'Test charge',
        quantity: 1,
        unit_price: 10000.5, // Not an integer
        total_price: 10000,
        tax_amount: 1000,
        net_amount: 9000,
        rate: 10000,
        is_manual: true,
      };

      await expect(Invoice.addInvoiceCharge(knex, 'tenant-123', charge)).rejects.toThrow(
        'Unit price must be an integer'
      );
    });

    it('should throw error when tax_amount is not an integer', async () => {
      const { knex } = createMockKnex();
      const charge = {
        invoice_id: 'invoice-123',
        description: 'Test charge',
        quantity: 1,
        unit_price: 10000,
        total_price: 10000,
        tax_amount: 1000.5, // Not an integer
        net_amount: 9000,
        rate: 10000,
        is_manual: true,
      };

      await expect(Invoice.addInvoiceCharge(knex, 'tenant-123', charge)).rejects.toThrow(
        'Tax amount must be an integer'
      );
    });

    it('should throw error when net_amount is not an integer', async () => {
      const { knex } = createMockKnex();
      const charge = {
        invoice_id: 'invoice-123',
        description: 'Test charge',
        quantity: 1,
        unit_price: 10000,
        total_price: 10000,
        tax_amount: 1000,
        net_amount: 9000.5, // Not an integer
        rate: 10000,
        is_manual: true,
      };

      await expect(Invoice.addInvoiceCharge(knex, 'tenant-123', charge)).rejects.toThrow(
        'Net amount must be an integer'
      );
    });
  });

  describe('getInvoiceCharges', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.getInvoiceCharges(knex, '', 'invoice-123')).rejects.toThrow(
        'Tenant context is required for getting invoice items'
      );
    });
  });

  describe('updateInvoiceCharge', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.updateInvoiceCharge(knex, '', 'item-123', {})).rejects.toThrow(
        'Tenant context is required for updating invoice item'
      );
    });
  });

  describe('deleteInvoiceItem', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.deleteInvoiceItem(knex, '', 'item-123')).rejects.toThrow(
        'Tenant context is required for deleting invoice item'
      );
    });
  });

  describe('getTemplates', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.getTemplates(knex, '')).rejects.toThrow(
        'Tenant context is required for getting templates'
      );
    });
  });

  describe('getAllTemplates', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.getAllTemplates(knex, '')).rejects.toThrow(
        'Tenant context is required for getting all templates'
      );
    });
  });

  describe('saveTemplate', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();
      const template = {
        template_id: 'template-123',
        name: 'Test Template',
        version: 1,
        templateAst: {
          kind: 'invoice-template-ast',
          version: 1,
          layout: { id: 'root', type: 'document', children: [] },
        },
      };

      await expect(Invoice.saveTemplate(knex, '', template)).rejects.toThrow(
        'Tenant context is required for saving template'
      );
    });
  });

  describe('generateInvoice', () => {
    it('should throw error when tenant is not provided', async () => {
      const { knex } = createMockKnex();

      await expect(Invoice.generateInvoice(knex, '', 'invoice-123')).rejects.toThrow(
        'Tenant context is required for generating invoice'
      );
    });
  });

  describe('deprecated methods', () => {
    it('addInvoiceItem should call addInvoiceCharge', async () => {
      const { knex } = createMockKnex();
      const charge = {
        invoice_id: 'invoice-123',
        description: 'Test charge',
        quantity: 1,
        unit_price: 10000,
        total_price: 10000,
        tax_amount: 1000,
        net_amount: 9000,
        rate: 10000,
        is_manual: true,
      };

      // Both should throw the same validation error since tenant is empty
      await expect(Invoice.addInvoiceItem(knex, '', charge)).rejects.toThrow(
        'Tenant context is required for adding invoice charge'
      );
    });

    it('getInvoiceItems should call getInvoiceCharges', async () => {
      const { knex } = createMockKnex();

      // Both should throw the same validation error since tenant is empty
      await expect(Invoice.getInvoiceItems(knex, '', 'invoice-123')).rejects.toThrow(
        'Tenant context is required for getting invoice items'
      );
    });

    it('updateInvoiceItem should call updateInvoiceCharge', async () => {
      const { knex } = createMockKnex();

      // Both should throw the same validation error since tenant is empty
      await expect(Invoice.updateInvoiceItem(knex, '', 'item-123', {})).rejects.toThrow(
        'Tenant context is required for updating invoice item'
      );
    });
  });
});
