import { NetaClawDataSourceEntity } from '../src/modules/netaclaw/entity/data_source.js';
import { MysqlPoolManager } from '../src/modules/netaclaw/service/mysql_pool.js';

function makeDataSource(overrides: Partial<NetaClawDataSourceEntity> = {}) {
  return Object.assign(new NetaClawDataSourceEntity(), {
    id: 7,
    name: 'orders',
    label: 'Orders DB',
    type: 'mysql',
    host: 'db.internal',
    port: 3307,
    database: 'orders_prod',
    username: 'readonly',
    passwordEncrypted: 'ciphertext',
    readonly: true,
    status: 1,
    allowedAgentIds: [9],
    extra: {
      poolConnectionLimit: 20,
      connectTimeout: 500,
      ssl: { rejectUnauthorized: true },
    },
    ...overrides,
  });
}

describe('MysqlPoolManager', () => {
  it('reuses pools by data source id, clamps connection limits, and decrypts passwords', async () => {
    const pool = { end: jest.fn() };
    const createPool = jest.fn().mockReturnValue(pool);
    const manager = new MysqlPoolManager(createPool as any);
    manager.secretCrypto = {
      decryptText: jest.fn().mockReturnValue('plain-password'),
    } as any;

    const first = await manager.getPool(makeDataSource());
    const second = await manager.getPool(makeDataSource({ host: 'other.internal' }));

    expect(first).toBe(pool);
    expect(second).toBe(pool);
    expect(createPool).toHaveBeenCalledTimes(1);
    expect(createPool).toHaveBeenCalledWith({
      host: 'db.internal',
      port: 3307,
      database: 'orders_prod',
      user: 'readonly',
      password: 'plain-password',
      waitForConnections: true,
      connectionLimit: 10,
      connectTimeout: 1000,
      ssl: { rejectUnauthorized: true },
    });
  });

  it('closes and forgets cached pools', async () => {
    const firstPool = { end: jest.fn().mockResolvedValue(undefined) };
    const secondPool = { end: jest.fn().mockResolvedValue(undefined) };
    const createPool = jest.fn().mockReturnValueOnce(firstPool).mockReturnValueOnce(secondPool);
    const manager = new MysqlPoolManager(createPool as any);
    manager.secretCrypto = {
      decryptText: jest.fn().mockReturnValue('plain-password'),
    } as any;

    await manager.getPool(makeDataSource());
    await manager.closePool(7);
    const next = await manager.getPool(makeDataSource());

    expect(firstPool.end).toHaveBeenCalledTimes(1);
    expect(next).toBe(secondPool);
    expect(createPool).toHaveBeenCalledTimes(2);
  });

  it('creates transient pools without caching and clamps transient connection limits', async () => {
    const firstPool = { end: jest.fn() };
    const secondPool = { end: jest.fn() };
    const createPool = jest.fn().mockReturnValueOnce(firstPool).mockReturnValueOnce(secondPool);
    const manager = new MysqlPoolManager(createPool as any);
    manager.secretCrypto = {
      decryptText: jest.fn().mockReturnValue('plain-password'),
    } as any;

    const first = await manager.createTransientPool(makeDataSource());
    const second = await manager.createTransientPool(makeDataSource());

    expect(first).toBe(firstPool);
    expect(second).toBe(secondPool);
    expect(createPool).toHaveBeenCalledTimes(2);
    expect(createPool).toHaveBeenLastCalledWith(
      expect.objectContaining({
        connectionLimit: 3,
        password: 'plain-password',
      })
    );
  });
});
