// src/services/db/address.db.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { Pool } from 'pg';
import { mockPoolInstance } from '../../tests/setup/tests-setup-unit';
import { AddressRepository } from './address.db';
import type { Address } from '../../types';
import { UniqueConstraintError, NotFoundError } from './errors.db';

// Un-mock the module we are testing
vi.unmock('./address.db');

// Mock dependencies
vi.mock('../logger.server', () => ({
  logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}));
import { logger as mockLogger } from '../logger.server';

describe('Address DB Service', () => {
  let addressRepo: AddressRepository;

  beforeEach(() => {
    vi.clearAllMocks();
    addressRepo = new AddressRepository(mockPoolInstance as unknown as Pool);
  });

  describe('getAddressById', () => {
    it('should return an address if found', async () => {
      const mockAddress: Address = { address_id: 1, address_line_1: '123 Main St', city: 'Anytown', province_state: 'CA', postal_code: '12345', country: 'USA' };
      mockPoolInstance.query.mockResolvedValue({ rows: [mockAddress] });
 
      const result = await addressRepo.getAddressById(1, mockLogger);

      expect(result).toEqual(mockAddress);
      expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.addresses WHERE address_id = $1', [1]);
    });

    it('should throw NotFoundError if no address is found', async () => {
      mockPoolInstance.query.mockResolvedValue({ rowCount: 0, rows: [] });
      await expect(addressRepo.getAddressById(999, mockLogger)).rejects.toThrow(NotFoundError);
      await expect(addressRepo.getAddressById(999, mockLogger)).rejects.toThrow('Address with ID 999 not found.');
    });

    it('should throw an error if the database query fails', async () => {
      const dbError = new Error('DB Error');
      mockPoolInstance.query.mockRejectedValue(dbError);

      await expect(addressRepo.getAddressById(1, mockLogger)).rejects.toThrow('Failed to retrieve address.');
      expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, addressId: 1 }, 'Database error in getAddressById');
    });
  });

  describe('upsertAddress', () => {
    it('should INSERT a new address when no address_id is provided', async () => {
      const newAddressData = { address_line_1: '456 New Ave', city: 'Newville' };
      mockPoolInstance.query.mockResolvedValue({ rows: [{ address_id: 2 }] });

      const result = await addressRepo.upsertAddress(newAddressData, mockLogger);

      expect(result).toBe(2);
      const [query, values] = mockPoolInstance.query.mock.calls[0];
      expect(query).toContain('INSERT INTO public.addresses');
      expect(query).toContain('ON CONFLICT (address_id) DO UPDATE');
      expect(values).toEqual(['456 New Ave', 'Newville']);
    });

    it('should UPDATE an existing address when an address_id is provided', async () => {
      const existingAddressData = { address_id: 1, address_line_1: '789 Old Rd', city: 'Oldtown' };
      mockPoolInstance.query.mockResolvedValue({ rows: [{ address_id: 1 }] });

      const result = await addressRepo.upsertAddress(existingAddressData, mockLogger);

      expect(result).toBe(1);
      const [query, values] = mockPoolInstance.query.mock.calls[0];
      expect(query).toContain('INSERT INTO public.addresses');
      expect(query).toContain('ON CONFLICT (address_id) DO UPDATE');
      // The values array should now include the address_id at the beginning
      expect(values).toEqual([1, '789 Old Rd', 'Oldtown']);
    });

    it('should throw a generic error on INSERT failure', async () => {
      const newAddressData = { address_line_1: '456 New Ave', city: 'Newville' };
      const dbError = new Error('DB Error');
      mockPoolInstance.query.mockRejectedValue(dbError);

      await expect(addressRepo.upsertAddress(newAddressData, mockLogger)).rejects.toThrow('Failed to upsert address.');
      expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, address: newAddressData }, 'Database error in upsertAddress');
    });

    it('should throw a generic error on UPDATE failure', async () => {
      const existingAddressData = { address_id: 1, address_line_1: '789 Old Rd', city: 'Oldtown' };
      const dbError = new Error('DB Error');
      mockPoolInstance.query.mockRejectedValue(dbError);

      await expect(addressRepo.upsertAddress(existingAddressData, mockLogger)).rejects.toThrow('Failed to upsert address.');
      expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, address: existingAddressData }, 'Database error in upsertAddress');
    });

    it('should throw UniqueConstraintError on duplicate address insert', async () => {
      const newAddressData = { address_line_1: '123 Main St', city: 'Anytown' };
      const dbError = new Error('duplicate key value violates unique constraint') as Error & { code: string };
      dbError.code = '23505';
      mockPoolInstance.query.mockRejectedValue(dbError);

      await expect(addressRepo.upsertAddress(newAddressData, mockLogger)).rejects.toThrow(UniqueConstraintError);
      await expect(addressRepo.upsertAddress(newAddressData, mockLogger)).rejects.toThrow('An identical address already exists.');
      expect(mockLogger.error).toHaveBeenCalledWith({ err: dbError, address: newAddressData }, 'Database error in upsertAddress');
    });
  });
});