// --- FIX REGISTRY ---
//
// // 2025-12-09: Fixed "TypeError: ... is not a constructor" in `queueService` and `connection.db` tests.
//             ISSUE: `vi.fn(() => ...)` creates a mock implementation using an arrow function.
//                    Arrow functions cannot be instantiated with `new`.
//             FIX:   Changed mock implementations to `vi.fn(function() { ... })`. Standard functions
//                    have a `[[Construct]]` method and support `new`.
//
// 2025-12-09: Addressed "Cannot access before initialization" in `auth.routes.test.ts`.
//             ISSUE: `vi.mock` is hoisted above top-level `import` statements. Referencing imported
//                    variables (like `db` or `types`) inside the mock factory fails.
//             FIX:   Moved variable creation inside `vi.hoisted` or the mock factory itself,
//                    removing the dependency on the top-level import within the mock definition.
// 
// 2025-12-09: Explicitly mocked 'pg' module using `vi.hoisted` and `vi.mock` within this test file.
//             This ensures `Pool` is a proper Vitest spy, allowing `expect(Pool).toHaveBeenCalledTimes(1)`
//             and `mockImplementation` overrides to work correctly, resolving "not a spy" errors.
//
// 2024-08-01: Corrected tests to assert against the globally mocked `mockPoolInstance` instead of spying
//             on the `pg.Pool` constructor. This aligns the test with the global mock setup in
//             `tests-setup-unit.ts` and fixes incorrect assertions.
//
// --- END FIX REGISTRY ---
// src/services/db/connection.db.test.ts
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';

// Define mocks first using vi.hoisted to allow usage in vi.mock factory
const mocks = vi.hoisted(() => {
  const mockPoolInstance = {
    connect: vi.fn(),
    query: vi.fn(),
    end: vi.fn(),
    on: vi.fn(),
    totalCount: 10,
    idleCount: 5,
    waitingCount: 0,
  };

  // FIX: Use a standard function expression so it can be called with 'new'
  const MockPool = vi.fn(function() {
    return mockPoolInstance;
  });

  return {
    mockPoolInstance,
    MockPool,
  };
});

// Mock the 'pg' module
vi.mock('pg', () => {
  return {
    Pool: mocks.MockPool,
    types: { setTypeParser: vi.fn(), builtins: { NUMERIC: 'NUMERIC', INT8: 'INT8' } },
  };
});

// Mock the logger. This is a server-side DB test.
vi.mock('../logger.server', () => ({
  logger: {
    info: vi.fn(),
    error: vi.fn(),
  },
}));
import { logger } from '../logger.server';

describe('DB Connection Service', () => {
  beforeEach(async () => {
    vi.clearAllMocks();
    
    // We reset modules to ensure 'connection.db.ts' re-evaluates and calls the (mocked) Pool constructor again
    // Reset implementations to default for shared mocks
    // This is crucial because 'should throw an error' test overrides implementation
    mocks.MockPool.mockImplementation(function() {
      return mocks.mockPoolInstance;
    });
    
    // Reset specific method behaviors
    mocks.mockPoolInstance.query.mockReset();

    vi.resetModules();
  });

  describe('getPool', () => {
    it('should create a new pool instance on the first call', async () => {
      // Re-import to ensure fresh state after resetModules
      const { getPool } = await import('./connection.db');
      const { Pool } = await import('pg'); // Get the current mock
      
      const pool = getPool();

      // Verify Pool constructor was called
      expect(Pool).toHaveBeenCalledTimes(1);
      expect(pool).toBeDefined();
      // Check basic shape to confirm it's our mock
      expect(pool).toHaveProperty('connect');
      expect(pool).toHaveProperty('query');
    });

    it('should return the same pool instance on subsequent calls', async () => {
      const { getPool } = await import('./connection.db');
      
      const pool1 = getPool();
      const pool2 = getPool();

      // Since getPool() caches the instance, and resetModules() was called in beforeEach,
      // this test starts fresh.
      // pool1 triggers new Pool(). pool2 returns cached pool1.
      expect(pool1).toBe(pool2);
    });
  });

  it('should throw an error if the Pool constructor fails', async () => {
    // Arrange: Mock the Pool constructor to throw an error
    const { Pool } = await import('pg');
    const constructorError = new Error('Invalid credentials');
    
    // FIX: Use standard function for mock implementation
    vi.mocked(Pool).mockImplementation(function() {
      throw constructorError;
    });

    const { getPool } = await import('./connection.db');

    // Act & Assert: Expect getPool to throw the error from the constructor
    expect(() => getPool()).toThrow(constructorError);
  });

  describe('checkTablesExist', () => {
    it('should return an empty array if all tables exist', async () => {
      const { checkTablesExist } = await import('./connection.db');
      const pool = mocks.mockPoolInstance;
      
      // Use vi.mocked() to get a type-safe mock of the query function
      (pool.query as Mock).mockResolvedValue({ rows: [{ table_name: 'users' }, { table_name: 'flyers' }] });

      const tableNames = ['users', 'flyers'];
      const missingTables = await checkTablesExist(tableNames);

      expect(pool.query).toHaveBeenCalledWith(expect.any(String), [tableNames]);
      expect(missingTables).toEqual([]);
    });

    it('should throw an error if the database query fails', async () => {
      const { checkTablesExist } = await import('./connection.db');
      const pool = mocks.mockPoolInstance;
      const dbError = new Error('DB Connection Failed');
      (pool.query as Mock).mockRejectedValue(dbError);

      const tableNames = ['users'];

      await expect(checkTablesExist(tableNames)).rejects.toThrow('Failed to check for tables in database.');
      expect(logger.error).toHaveBeenCalledWith({ err: dbError }, 'Database error in checkTablesExist');
    });

    it('should return an array of missing tables', async () => {
      const { checkTablesExist } = await import('./connection.db');
      const pool = mocks.mockPoolInstance;
      
      (pool.query as Mock).mockResolvedValue({ rows: [{ table_name: 'users' }] });

      const tableNames = ['users', 'flyers', 'products'];
      const missingTables = await checkTablesExist(tableNames);

      expect(missingTables).toEqual(['flyers', 'products']);
    });
  });

  describe('getPoolStatus', () => {
    it('should return the status counts from the pool instance', async () => {
      const { getPoolStatus } = await import('./connection.db');
      const status = getPoolStatus();
      
      expect(status).toEqual({
        totalCount: 10,
        idleCount: 5,
        waitingCount: 0,
      });
    });
  });

  describe('withTransaction', () => {
    it('should commit the transaction on success', async () => {
      const { withTransaction } = await import('./connection.db');
      const mockClient = {
        query: vi.fn().mockResolvedValue({}),
        release: vi.fn(),
      };
      mocks.mockPoolInstance.connect.mockResolvedValue(mockClient);

      const callback = vi.fn().mockResolvedValue('success');
      const result = await withTransaction(callback);

      expect(result).toBe('success');
      expect(mockClient.query).toHaveBeenCalledWith('BEGIN');
      expect(callback).toHaveBeenCalledWith(mockClient);
      expect(mockClient.query).toHaveBeenCalledWith('COMMIT');
      expect(mockClient.release).toHaveBeenCalledTimes(1);
    });

    it('should rollback the transaction and log on error', async () => {
      const { withTransaction } = await import('./connection.db');
      const mockClient = {
        query: vi.fn().mockResolvedValue({}),
        release: vi.fn(),
      };
      mocks.mockPoolInstance.connect.mockResolvedValue(mockClient);

      const dbError = new Error('Callback failed');
      const callback = vi.fn().mockRejectedValue(dbError);

      await expect(withTransaction(callback)).rejects.toThrow(dbError);

      expect(mockClient.query).toHaveBeenCalledWith('BEGIN');
      expect(callback).toHaveBeenCalledWith(mockClient);
      expect(mockClient.query).toHaveBeenCalledWith('ROLLBACK');
      expect(mockClient.query).not.toHaveBeenCalledWith('COMMIT');
      expect(logger.error).toHaveBeenCalledWith({ err: dbError }, 'Transaction failed, rolling back.');
      expect(mockClient.release).toHaveBeenCalledTimes(1);
    });
  });
});