/**
 * Sales Flow Integration Tests
 *
 * Tests the complete Order-to-Cash flow:
 * 1. Quotation -> Approval -> Sales Order conversion
 * 2. Sales Order -> Picking -> Delivery confirmation
 * 3. Delivery -> Invoice generation
 * 4. Order cancellation and credit note scenarios
 * 5. Pricelist application and discounts
 */

import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import {
  createMockQuotation,
  createMockQuotationLine,
  createMockSalesOrder,
  createMockSalesOrderLine,
} from '../../../__tests__/helpers.js';

// Mock query functions
const mockQuery = jest.fn();
const mockQueryOne = jest.fn();
const mockGetClient = jest.fn();

// Create a mock client for transaction handling
const createMockClient = () => ({
  query: jest.fn(),
  release: jest.fn(),
});

jest.mock('../../../config/database.js', () => ({
  query: (...args: any[]) => mockQuery(...args),
  queryOne: (...args: any[]) => mockQueryOne(...args),
  getClient: () => mockGetClient(),
}));

// Mock taxesService
jest.mock('../../financial/taxes.service.js', () => ({
  taxesService: {
    calculateTaxes: jest.fn(() => Promise.resolve({
      amountUntaxed: 1000,
      amountTax: 160,
      amountTotal: 1160,
    })),
  },
}));

// Mock sequencesService
jest.mock('../../core/sequences.service.js', () => ({
  sequencesService: {
    getNextNumber: jest.fn((code: string) => {
      if (code === 'SO') return Promise.resolve('SO-000001');
      if (code === 'WH/OUT') return Promise.resolve('WH/OUT/000001');
      return Promise.resolve('SEQ-000001');
    }),
  },
  SEQUENCE_CODES: {
    SALES_ORDER: 'SO',
    PICKING_OUT: 'WH/OUT',
  },
}));

// Mock stockReservationService
const mockReserveWithClient = jest.fn();
const mockReleaseWithClient = jest.fn();

jest.mock('../../inventory/stock-reservation.service.js', () => ({
  stockReservationService: {
    reserveWithClient: (...args: any[]) => mockReserveWithClient(...args),
    releaseWithClient: (...args: any[]) => mockReleaseWithClient(...args),
    checkAvailability: jest.fn(() => Promise.resolve({ available: true, lines: [] })),
  },
}));

// Mock logger
jest.mock('../../../shared/utils/logger.js', () => ({
  logger: {
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
    debug: jest.fn(),
  },
}));

// Import services after mocking
import { quotationsService } from '../quotations.service.js';
import { ordersService } from '../orders.service.js';
import { pricelistsService } from '../pricelists.service.js';
import { NotFoundError, ValidationError } from '../../../shared/errors/index.js';

describe('Sales Flow Integration Tests', () => {
  const tenantId = 'test-tenant-uuid';
  const userId = 'test-user-uuid';
  const companyId = 'company-uuid-1';
  const partnerId = 'partner-uuid-1';
  const currencyId = 'currency-uuid-1';
  const productId = 'product-uuid-1';

  beforeEach(() => {
    jest.clearAllMocks();
    mockReserveWithClient.mockResolvedValue({ success: true, errors: [] });
    mockReleaseWithClient.mockResolvedValue(undefined);
  });

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

  describe('Flow 1: Quotation -> Approval -> Sales Order Conversion', () => {
    it('should create quotation, add lines, and convert to sales order', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      // Step 1: Create quotation
      const quotationData = createMockQuotation({
        id: 'quotation-uuid',
        name: 'QUO-000001',
        status: 'draft',
        company_id: companyId,
        partner_id: partnerId,
        currency_id: currencyId,
      });

      mockQueryOne
        .mockResolvedValueOnce({ next_num: 1 }) // sequence for quotation
        .mockResolvedValueOnce(quotationData); // INSERT quotation

      const createdQuotation = await quotationsService.create({
        company_id: companyId,
        partner_id: partnerId,
        currency_id: currencyId,
        validity_date: '2024-12-31',
      }, tenantId, userId);

      expect(createdQuotation.name).toBe('QUO-000001');
      expect(createdQuotation.status).toBe('draft');

      // Step 2: Add line to quotation
      const lineData = createMockQuotationLine({
        id: 'line-uuid',
        quotation_id: 'quotation-uuid',
        product_id: productId,
        quantity: 10,
        price_unit: 100,
      });

      mockQueryOne
        .mockResolvedValueOnce(quotationData) // findById for addLine
        .mockResolvedValueOnce(lineData); // INSERT line
      mockQuery
        .mockResolvedValueOnce([]) // lines for findById
        .mockResolvedValueOnce(undefined); // updateTotals

      const addedLine = await quotationsService.addLine('quotation-uuid', {
        product_id: productId,
        description: 'Test Product',
        quantity: 10,
        uom_id: 'uom-uuid',
        price_unit: 100,
      }, tenantId, userId);

      expect(addedLine.product_id).toBe(productId);

      // Step 3: Confirm quotation (convert to sales order)
      const quotationWithLines = {
        ...quotationData,
        lines: [lineData],
      };

      mockQueryOne
        .mockResolvedValueOnce(quotationWithLines) // findById for confirm
        .mockResolvedValueOnce(quotationWithLines); // findById after confirm
      mockQuery
        .mockResolvedValueOnce([lineData]) // lines for first findById
        .mockResolvedValueOnce([lineData]); // lines for second findById

      mockClient.query
        .mockResolvedValueOnce(undefined) // BEGIN
        .mockResolvedValueOnce({ rows: [{ next_num: 1 }] }) // order sequence
        .mockResolvedValueOnce({ rows: [{ id: 'order-uuid' }] }) // INSERT order
        .mockResolvedValueOnce(undefined) // INSERT order lines
        .mockResolvedValueOnce(undefined) // UPDATE quotation
        .mockResolvedValueOnce(undefined); // COMMIT

      const { quotation, orderId } = await quotationsService.confirm('quotation-uuid', tenantId, userId);

      expect(orderId).toBe('order-uuid');
      expect(mockClient.query).toHaveBeenCalledWith('BEGIN');
      expect(mockClient.query).toHaveBeenCalledWith('COMMIT');
    });

    it('should not convert quotation without lines', async () => {
      const quotationWithoutLines = createMockQuotation({
        id: 'quotation-uuid',
        status: 'draft',
        lines: [],
      });

      mockQueryOne.mockResolvedValue(quotationWithoutLines);
      mockQuery.mockResolvedValue([]); // no lines

      await expect(
        quotationsService.confirm('quotation-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should not convert already confirmed quotation', async () => {
      const confirmedQuotation = createMockQuotation({
        id: 'quotation-uuid',
        status: 'confirmed',
      });

      mockQueryOne.mockResolvedValue(confirmedQuotation);
      mockQuery.mockResolvedValue([]);

      await expect(
        quotationsService.confirm('quotation-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should convert sent quotation to sales order', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const sentQuotation = createMockQuotation({
        id: 'quotation-uuid',
        status: 'sent',
        lines: [createMockQuotationLine()],
      });

      mockQueryOne
        .mockResolvedValueOnce(sentQuotation)
        .mockResolvedValueOnce(sentQuotation);
      mockQuery.mockResolvedValue([createMockQuotationLine()]);

      mockClient.query
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce({ rows: [{ next_num: 1 }] })
        .mockResolvedValueOnce({ rows: [{ id: 'order-uuid' }] })
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined);

      const { orderId } = await quotationsService.confirm('quotation-uuid', tenantId, userId);

      expect(orderId).toBe('order-uuid');
    });
  });

  describe('Flow 2: Sales Order -> Picking -> Delivery Confirmation', () => {
    it('should confirm sales order and create picking', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const orderWithLines = createMockSalesOrder({
        id: 'order-uuid',
        name: 'SO-000001',
        status: 'draft',
        company_id: companyId,
        lines: [createMockSalesOrderLine({ product_id: productId, quantity: 5 })],
      });

      mockQueryOne
        .mockResolvedValueOnce(orderWithLines) // findById before confirm
        .mockResolvedValueOnce({ ...orderWithLines, status: 'sent', picking_id: 'picking-uuid' }); // findById after confirm
      mockQuery
        .mockResolvedValueOnce([createMockSalesOrderLine({ product_id: productId, quantity: 5 })]) // lines before
        .mockResolvedValueOnce([createMockSalesOrderLine({ product_id: productId, quantity: 5 })]); // lines after

      mockClient.query
        .mockResolvedValueOnce({ rows: [] }) // BEGIN
        .mockResolvedValueOnce({ rows: [{ location_id: 'stock-location-uuid', warehouse_id: 'warehouse-uuid' }] }) // location query
        .mockResolvedValueOnce({ rows: [{ id: 'customer-location-uuid' }] }) // customer location
        .mockResolvedValueOnce({ rows: [{ id: 'picking-uuid' }] }) // INSERT picking
        .mockResolvedValueOnce({ rows: [] }) // INSERT stock_move
        .mockResolvedValueOnce({ rows: [] }) // UPDATE order status
        .mockResolvedValueOnce({ rows: [] }); // COMMIT

      const confirmedOrder = await ordersService.confirm('order-uuid', tenantId, userId);

      expect(confirmedOrder.status).toBe('sent');
      expect(confirmedOrder.picking_id).toBe('picking-uuid');
      expect(mockReserveWithClient).toHaveBeenCalled();
    });

    it('should fail confirmation if stock is insufficient', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      mockReserveWithClient.mockResolvedValue({
        success: false,
        errors: ['Insufficient stock for product Test Product'],
      });

      const orderWithLines = createMockSalesOrder({
        id: 'order-uuid',
        status: 'draft',
        company_id: companyId,
        lines: [createMockSalesOrderLine({ product_id: productId, quantity: 1000 })],
      });

      mockQueryOne.mockResolvedValue(orderWithLines);
      mockQuery.mockResolvedValue([createMockSalesOrderLine({ product_id: productId, quantity: 1000 })]);

      mockClient.query
        .mockResolvedValueOnce({ rows: [] }) // BEGIN
        .mockResolvedValueOnce({ rows: [{ location_id: 'stock-location-uuid', warehouse_id: 'warehouse-uuid' }] })
        .mockResolvedValueOnce({ rows: [{ id: 'customer-location-uuid' }] });

      await expect(
        ordersService.confirm('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);

      expect(mockClient.query).toHaveBeenCalledWith('ROLLBACK');
    });

    it('should not confirm order without lines', async () => {
      const orderWithoutLines = createMockSalesOrder({
        id: 'order-uuid',
        status: 'draft',
        lines: [],
      });

      mockQueryOne.mockResolvedValue(orderWithoutLines);
      mockQuery.mockResolvedValue([]);

      await expect(
        ordersService.confirm('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should not confirm already confirmed order', async () => {
      const confirmedOrder = createMockSalesOrder({
        id: 'order-uuid',
        status: 'sent',
      });

      mockQueryOne.mockResolvedValue(confirmedOrder);
      mockQuery.mockResolvedValue([]);

      await expect(
        ordersService.confirm('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should create customer location if not exists', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const orderWithLines = createMockSalesOrder({
        id: 'order-uuid',
        status: 'draft',
        company_id: companyId,
        lines: [createMockSalesOrderLine()],
      });

      mockQueryOne
        .mockResolvedValueOnce(orderWithLines)
        .mockResolvedValueOnce({ ...orderWithLines, status: 'sent' });
      mockQuery.mockResolvedValue([createMockSalesOrderLine()]);

      mockClient.query
        .mockResolvedValueOnce({ rows: [] }) // BEGIN
        .mockResolvedValueOnce({ rows: [{ location_id: 'stock-location-uuid', warehouse_id: 'warehouse-uuid' }] })
        .mockResolvedValueOnce({ rows: [] }) // no customer location
        .mockResolvedValueOnce({ rows: [{ id: 'new-customer-location-uuid' }] }) // INSERT customer location
        .mockResolvedValueOnce({ rows: [{ id: 'picking-uuid' }] })
        .mockResolvedValueOnce({ rows: [] })
        .mockResolvedValueOnce({ rows: [] })
        .mockResolvedValueOnce({ rows: [] });

      await ordersService.confirm('order-uuid', tenantId, userId);

      // Verify customer location creation query was called
      // The INSERT statement should contain inventory.locations
      const calls = mockClient.query.mock.calls;
      const insertLocationCall = calls.find((call: any[]) =>
        typeof call[0] === 'string' && call[0].includes('INSERT INTO inventory.locations')
      );
      expect(insertLocationCall).toBeDefined();
    });
  });

  describe('Flow 3: Delivery Confirmed -> Invoice Generation', () => {
    it('should create invoice from confirmed order (policy=order)', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const confirmedOrder = createMockSalesOrder({
        id: 'order-uuid',
        name: 'SO-000001',
        status: 'sent',
        invoice_status: 'pending',
        invoice_policy: 'order',
        company_id: companyId,
        currency_id: currencyId,
        partner_id: partnerId,
        lines: [createMockSalesOrderLine({
          id: 'line-uuid',
          quantity: 10,
          qty_invoiced: 0,
          price_unit: 100,
          discount: 0,
        })],
      });

      mockQueryOne.mockResolvedValue(confirmedOrder);
      mockQuery.mockResolvedValue([createMockSalesOrderLine({
        id: 'line-uuid',
        quantity: 10,
        qty_invoiced: 0,
        price_unit: 100,
        discount: 0,
      })]);

      mockClient.query
        .mockResolvedValueOnce(undefined) // BEGIN
        .mockResolvedValueOnce({ rows: [{ next_num: 1 }] }) // invoice sequence
        .mockResolvedValueOnce({ rows: [{ id: 'invoice-uuid' }] }) // INSERT invoice
        .mockResolvedValueOnce(undefined) // INSERT invoice line
        .mockResolvedValueOnce(undefined) // UPDATE qty_invoiced
        .mockResolvedValueOnce(undefined) // UPDATE invoice totals
        .mockResolvedValueOnce(undefined) // UPDATE order status
        .mockResolvedValueOnce(undefined); // COMMIT

      const { invoiceId } = await ordersService.createInvoice('order-uuid', tenantId, userId);

      expect(invoiceId).toBe('invoice-uuid');
      expect(mockClient.query).toHaveBeenCalledWith('COMMIT');
    });

    it('should create invoice based on delivered quantity (policy=delivery)', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const orderWithDelivery = createMockSalesOrder({
        id: 'order-uuid',
        status: 'sent',
        invoice_status: 'pending',
        invoice_policy: 'delivery',
        lines: [createMockSalesOrderLine({
          id: 'line-uuid',
          quantity: 10,
          qty_delivered: 5,
          qty_invoiced: 0,
          price_unit: 100,
        })],
      });

      mockQueryOne.mockResolvedValue(orderWithDelivery);
      mockQuery.mockResolvedValue([createMockSalesOrderLine({
        id: 'line-uuid',
        quantity: 10,
        qty_delivered: 5,
        qty_invoiced: 0,
        price_unit: 100,
      })]);

      mockClient.query
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce({ rows: [{ next_num: 1 }] })
        .mockResolvedValueOnce({ rows: [{ id: 'invoice-uuid' }] })
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined);

      const { invoiceId } = await ordersService.createInvoice('order-uuid', tenantId, userId);

      expect(invoiceId).toBe('invoice-uuid');
    });

    it('should not invoice draft order', async () => {
      const draftOrder = createMockSalesOrder({
        id: 'order-uuid',
        status: 'draft',
      });

      mockQueryOne.mockResolvedValue(draftOrder);
      mockQuery.mockResolvedValue([]);

      await expect(
        ordersService.createInvoice('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should not invoice fully invoiced order', async () => {
      const fullyInvoicedOrder = createMockSalesOrder({
        id: 'order-uuid',
        status: 'sent',
        invoice_status: 'invoiced',
      });

      mockQueryOne.mockResolvedValue(fullyInvoicedOrder);
      mockQuery.mockResolvedValue([]);

      await expect(
        ordersService.createInvoice('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should not invoice when no lines to invoice', async () => {
      const orderFullyInvoicedLines = createMockSalesOrder({
        id: 'order-uuid',
        status: 'sent',
        invoice_status: 'pending',
        invoice_policy: 'order',
        lines: [createMockSalesOrderLine({
          quantity: 10,
          qty_invoiced: 10, // fully invoiced
        })],
      });

      mockQueryOne.mockResolvedValue(orderFullyInvoicedLines);
      mockQuery.mockResolvedValue([createMockSalesOrderLine({
        quantity: 10,
        qty_invoiced: 10,
      })]);

      await expect(
        ordersService.createInvoice('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should handle partial invoicing', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      // First invoice - partial
      const partiallyInvoicedOrder = createMockSalesOrder({
        id: 'order-uuid',
        status: 'sent',
        invoice_status: 'pending',
        invoice_policy: 'order',
        lines: [createMockSalesOrderLine({
          id: 'line-uuid',
          quantity: 10,
          qty_invoiced: 5, // partially invoiced
          price_unit: 100,
        })],
      });

      mockQueryOne.mockResolvedValue(partiallyInvoicedOrder);
      mockQuery.mockResolvedValue([createMockSalesOrderLine({
        id: 'line-uuid',
        quantity: 10,
        qty_invoiced: 5,
        price_unit: 100,
      })]);

      mockClient.query
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce({ rows: [{ next_num: 2 }] })
        .mockResolvedValueOnce({ rows: [{ id: 'invoice-uuid-2' }] })
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce(undefined);

      const { invoiceId } = await ordersService.createInvoice('order-uuid', tenantId, userId);

      expect(invoiceId).toBe('invoice-uuid-2');
    });
  });

  describe('Flow 4: Order Cancellation', () => {
    it('should cancel draft order', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const draftOrder = createMockSalesOrder({
        id: 'order-uuid',
        status: 'draft',
        delivery_status: 'pending',
        invoice_status: 'pending',
      });

      mockQueryOne
        .mockResolvedValueOnce(draftOrder)
        .mockResolvedValueOnce({ ...draftOrder, status: 'cancelled' });
      mockQuery.mockResolvedValue([]);

      mockClient.query
        .mockResolvedValueOnce({ rows: [] }) // BEGIN
        .mockResolvedValueOnce({ rows: [] }) // UPDATE status
        .mockResolvedValueOnce({ rows: [] }); // COMMIT

      const cancelledOrder = await ordersService.cancel('order-uuid', tenantId, userId);

      expect(cancelledOrder.status).toBe('cancelled');
    });

    it('should cancel confirmed order and release stock', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const confirmedOrder = createMockSalesOrder({
        id: 'order-uuid',
        name: 'SO-000001',
        status: 'sent',
        delivery_status: 'pending',
        invoice_status: 'pending',
        picking_id: 'picking-uuid',
        lines: [createMockSalesOrderLine({ product_id: productId, quantity: 5 })],
      });

      mockQueryOne
        .mockResolvedValueOnce(confirmedOrder)
        .mockResolvedValueOnce({ ...confirmedOrder, status: 'cancelled' });
      mockQuery.mockResolvedValue([createMockSalesOrderLine({ product_id: productId, quantity: 5 })]);

      mockClient.query
        .mockResolvedValueOnce({ rows: [] }) // BEGIN
        .mockResolvedValueOnce({ rows: [{ location_id: 'stock-location-uuid' }] }) // get picking location
        .mockResolvedValueOnce({ rows: [] }) // UPDATE picking cancelled
        .mockResolvedValueOnce({ rows: [] }) // UPDATE stock_moves cancelled
        .mockResolvedValueOnce({ rows: [] }) // UPDATE order status
        .mockResolvedValueOnce({ rows: [] }); // COMMIT

      await ordersService.cancel('order-uuid', tenantId, userId);

      expect(mockReleaseWithClient).toHaveBeenCalled();
    });

    it('should not cancel completed order', async () => {
      const completedOrder = createMockSalesOrder({
        id: 'order-uuid',
        status: 'done',
      });

      mockQueryOne.mockResolvedValue(completedOrder);
      mockQuery.mockResolvedValue([]);

      await expect(
        ordersService.cancel('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should not cancel order with deliveries', async () => {
      const orderWithDeliveries = createMockSalesOrder({
        id: 'order-uuid',
        status: 'sent',
        delivery_status: 'partial',
        invoice_status: 'pending',
      });

      mockQueryOne.mockResolvedValue(orderWithDeliveries);
      mockQuery.mockResolvedValue([]);

      await expect(
        ordersService.cancel('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should not cancel order with invoices', async () => {
      const orderWithInvoices = createMockSalesOrder({
        id: 'order-uuid',
        status: 'sent',
        delivery_status: 'pending',
        invoice_status: 'partial',
      });

      mockQueryOne.mockResolvedValue(orderWithInvoices);
      mockQuery.mockResolvedValue([]);

      await expect(
        ordersService.cancel('order-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should cancel quotation in draft status', async () => {
      const draftQuotation = createMockQuotation({
        id: 'quotation-uuid',
        status: 'draft',
      });

      mockQueryOne.mockResolvedValue(draftQuotation);
      mockQuery
        .mockResolvedValueOnce([])
        .mockResolvedValueOnce(undefined);

      await quotationsService.cancel('quotation-uuid', tenantId, userId);

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining("status = 'cancelled'"),
        expect.any(Array)
      );
    });

    it('should not cancel confirmed quotation', async () => {
      const confirmedQuotation = createMockQuotation({
        id: 'quotation-uuid',
        status: 'confirmed',
      });

      mockQueryOne.mockResolvedValue(confirmedQuotation);
      mockQuery.mockResolvedValue([]);

      await expect(
        quotationsService.cancel('quotation-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });
  });

  describe('Flow 5: Pricelist Application and Discounts', () => {
    it('should get product price from pricelist', async () => {
      mockQueryOne.mockResolvedValue({ price: 85 }); // discounted price

      const price = await pricelistsService.getProductPrice(
        productId,
        'pricelist-uuid',
        10
      );

      expect(price).toBe(85);
    });

    it('should return higher discount for larger quantities', async () => {
      // First call with small quantity
      mockQueryOne.mockResolvedValueOnce({ price: 100 });

      const smallQtyPrice = await pricelistsService.getProductPrice(
        productId,
        'pricelist-uuid',
        1
      );

      expect(smallQtyPrice).toBe(100);

      // Second call with large quantity
      mockQueryOne.mockResolvedValueOnce({ price: 80 });

      const largeQtyPrice = await pricelistsService.getProductPrice(
        productId,
        'pricelist-uuid',
        100
      );

      expect(largeQtyPrice).toBe(80);
    });

    it('should return null when product not in pricelist', async () => {
      mockQueryOne.mockResolvedValue(null);

      const price = await pricelistsService.getProductPrice(
        'unknown-product',
        'pricelist-uuid',
        1
      );

      expect(price).toBeNull();
    });

    it('should respect pricelist validity dates', async () => {
      mockQueryOne.mockResolvedValue({ price: 90 });

      await pricelistsService.getProductPrice(productId, 'pricelist-uuid', 1);

      expect(mockQueryOne).toHaveBeenCalledWith(
        expect.stringContaining('valid_from IS NULL OR valid_from <= CURRENT_DATE'),
        expect.any(Array)
      );
    });
  });

  describe('Error Handling and Rollback', () => {
    it('should rollback on quotation confirmation error', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const quotation = createMockQuotation({
        status: 'draft',
        lines: [createMockQuotationLine()],
      });

      mockQueryOne.mockResolvedValue(quotation);
      mockQuery.mockResolvedValue([createMockQuotationLine()]);

      mockClient.query
        .mockResolvedValueOnce(undefined) // BEGIN
        .mockRejectedValueOnce(new Error('Database error')); // fail on sequence

      await expect(
        quotationsService.confirm('quotation-uuid', tenantId, userId)
      ).rejects.toThrow('Database error');

      expect(mockClient.query).toHaveBeenCalledWith('ROLLBACK');
      expect(mockClient.release).toHaveBeenCalled();
    });

    it('should rollback on order confirmation error', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const order = createMockSalesOrder({
        status: 'draft',
        company_id: companyId,
        lines: [createMockSalesOrderLine()],
      });

      mockQueryOne.mockResolvedValue(order);
      mockQuery.mockResolvedValue([createMockSalesOrderLine()]);

      mockClient.query
        .mockResolvedValueOnce({ rows: [] }) // BEGIN
        .mockResolvedValueOnce({ rows: [{ location_id: 'loc-uuid', warehouse_id: 'wh-uuid' }] })
        .mockResolvedValueOnce({ rows: [{ id: 'cust-loc-uuid' }] })
        .mockRejectedValueOnce(new Error('Picking creation failed'));

      await expect(
        ordersService.confirm('order-uuid', tenantId, userId)
      ).rejects.toThrow('Picking creation failed');

      expect(mockClient.query).toHaveBeenCalledWith('ROLLBACK');
    });

    it('should rollback on invoice creation error', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const order = createMockSalesOrder({
        status: 'sent',
        invoice_status: 'pending',
        invoice_policy: 'order',
        lines: [createMockSalesOrderLine({ quantity: 10, qty_invoiced: 0 })],
      });

      mockQueryOne.mockResolvedValue(order);
      mockQuery.mockResolvedValue([createMockSalesOrderLine({ quantity: 10, qty_invoiced: 0 })]);

      mockClient.query
        .mockResolvedValueOnce(undefined) // BEGIN
        .mockRejectedValueOnce(new Error('Sequence error'));

      await expect(
        ordersService.createInvoice('order-uuid', tenantId, userId)
      ).rejects.toThrow('Sequence error');

      expect(mockClient.query).toHaveBeenCalledWith('ROLLBACK');
    });
  });

  describe('State Transitions', () => {
    it('should transition quotation: draft -> sent', async () => {
      const draftQuotation = createMockQuotation({
        status: 'draft',
        lines: [createMockQuotationLine()],
      });

      mockQueryOne
        .mockResolvedValueOnce(draftQuotation)
        .mockResolvedValueOnce({ ...draftQuotation, status: 'sent' });
      mockQuery
        .mockResolvedValueOnce([createMockQuotationLine()])
        .mockResolvedValueOnce(undefined)
        .mockResolvedValueOnce([createMockQuotationLine()]);

      const sentQuotation = await quotationsService.send('quotation-uuid', tenantId, userId);

      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining("status = 'sent'"),
        expect.any(Array)
      );
    });

    it('should not send quotation without lines', async () => {
      const emptyQuotation = createMockQuotation({
        status: 'draft',
        lines: [],
      });

      mockQueryOne.mockResolvedValue(emptyQuotation);
      mockQuery.mockResolvedValue([]);

      await expect(
        quotationsService.send('quotation-uuid', tenantId, userId)
      ).rejects.toThrow(ValidationError);
    });

    it('should track order delivery_status transitions', async () => {
      // pending -> partial -> delivered
      const orderStates = [
        createMockSalesOrder({ delivery_status: 'pending' }),
        createMockSalesOrder({ delivery_status: 'partial' }),
        createMockSalesOrder({ delivery_status: 'delivered' }),
      ];

      for (const order of orderStates) {
        mockQueryOne.mockResolvedValueOnce(order);
        mockQuery.mockResolvedValueOnce([]);

        const result = await ordersService.findById('order-uuid', tenantId);
        expect(['pending', 'partial', 'delivered']).toContain(result.delivery_status);
      }
    });

    it('should track order invoice_status transitions', async () => {
      // pending -> partial -> invoiced
      const orderStates = [
        createMockSalesOrder({ invoice_status: 'pending' }),
        createMockSalesOrder({ invoice_status: 'partial' }),
        createMockSalesOrder({ invoice_status: 'invoiced' }),
      ];

      for (const order of orderStates) {
        mockQueryOne.mockResolvedValueOnce(order);
        mockQuery.mockResolvedValueOnce([]);

        const result = await ordersService.findById('order-uuid', tenantId);
        expect(['pending', 'partial', 'invoiced']).toContain(result.invoice_status);
      }
    });
  });

  describe('Multi-line Order Processing', () => {
    it('should handle order with multiple lines', async () => {
      const mockClient = createMockClient();
      mockGetClient.mockResolvedValue(mockClient);

      const multiLineOrder = createMockSalesOrder({
        id: 'order-uuid',
        status: 'draft',
        company_id: companyId,
        lines: [
          createMockSalesOrderLine({ id: 'line-1', product_id: 'product-1', quantity: 5 }),
          createMockSalesOrderLine({ id: 'line-2', product_id: 'product-2', quantity: 10 }),
          createMockSalesOrderLine({ id: 'line-3', product_id: 'product-3', quantity: 3 }),
        ],
      });

      const lines = [
        createMockSalesOrderLine({ id: 'line-1', product_id: 'product-1', quantity: 5 }),
        createMockSalesOrderLine({ id: 'line-2', product_id: 'product-2', quantity: 10 }),
        createMockSalesOrderLine({ id: 'line-3', product_id: 'product-3', quantity: 3 }),
      ];

      mockQueryOne
        .mockResolvedValueOnce(multiLineOrder)
        .mockResolvedValueOnce({ ...multiLineOrder, status: 'sent' });
      mockQuery
        .mockResolvedValueOnce(lines)
        .mockResolvedValueOnce(lines);

      mockClient.query
        .mockResolvedValueOnce({ rows: [] }) // BEGIN
        .mockResolvedValueOnce({ rows: [{ location_id: 'loc-uuid', warehouse_id: 'wh-uuid' }] })
        .mockResolvedValueOnce({ rows: [{ id: 'cust-loc-uuid' }] })
        .mockResolvedValueOnce({ rows: [{ id: 'picking-uuid' }] })
        .mockResolvedValueOnce({ rows: [] }) // stock_move for line 1
        .mockResolvedValueOnce({ rows: [] }) // stock_move for line 2
        .mockResolvedValueOnce({ rows: [] }) // stock_move for line 3
        .mockResolvedValueOnce({ rows: [] }) // UPDATE order
        .mockResolvedValueOnce({ rows: [] }); // COMMIT

      await ordersService.confirm('order-uuid', tenantId, userId);

      // Verify stock reservation was called with all lines
      expect(mockReserveWithClient).toHaveBeenCalledWith(
        mockClient,
        expect.arrayContaining([
          expect.objectContaining({ productId: 'product-1', quantity: 5 }),
          expect.objectContaining({ productId: 'product-2', quantity: 10 }),
          expect.objectContaining({ productId: 'product-3', quantity: 3 }),
        ]),
        tenantId,
        expect.any(String),
        false
      );
    });
  });
});
