import { describe, it, expect, afterEach } from 'vitest';
import { loadConfig, deepMerge } from './loader.js';
import { writeFileSync, mkdirSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';

describe('deepMerge', () => {
  it('merges nested objects', () => {
    const result = deepMerge({ a: { b: 1, c: 2 } }, { a: { c: 3 } });
    expect(result).toEqual({ a: { b: 1, c: 3 } });
  });

  it('overlay arrays replace base arrays', () => {
    const result = deepMerge({ a: [1, 2] }, { a: [3] });
    expect(result).toEqual({ a: [3] });
  });

  it('adds new keys from overlay', () => {
    const result = deepMerge({ a: 1 }, { b: 2 });
    expect(result).toEqual({ a: 1, b: 2 });
  });

  it('returns base when overlay is empty', () => {
    const result = deepMerge({ a: 1 }, {});
    expect(result).toEqual({ a: 1 });
  });

  it('handles deeply nested objects (3+ levels)', () => {
    const base = { a: { b: { c: { d: 1, e: 2 }, f: 3 } } };
    const overlay = { a: { b: { c: { e: 99 } } } };
    const result = deepMerge(base, overlay);
    expect(result).toEqual({ a: { b: { c: { d: 1, e: 99 }, f: 3 } } });
  });

  it('null in overlay sets key to null', () => {
    const result = deepMerge({ a: { b: 1 } }, { a: null as unknown as Record<string, unknown> });
    expect(result).toEqual({ a: null });
  });
});

describe('loadConfig', () => {
  const testDir = join(tmpdir(), 'flynn-test-config');

  it('loads valid config from file', () => {
    mkdirSync(testDir, { recursive: true });
    const configPath = join(testDir, 'config.yaml');
    writeFileSync(configPath, `
telegram:
  bot_token: "test-token"
  allowed_chat_ids: [123456789]
server:
  port: 18800
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    const config = loadConfig(configPath);

    expect(config.telegram?.bot_token).toBe('test-token');
    expect(config.telegram?.allowed_chat_ids).toContain(123456789);
    expect(config.server.port).toBe(18800);
    expect(config.models.default.provider).toBe('anthropic');

    rmSync(testDir, { recursive: true });
  });

  it('expands environment variables', () => {
    mkdirSync(testDir, { recursive: true });
    const configPath = join(testDir, 'config.yaml');
    process.env.TEST_BOT_TOKEN = 'env-token-value';
    writeFileSync(configPath, `
telegram:
  bot_token: \${TEST_BOT_TOKEN}
  allowed_chat_ids: [123]
server:
  port: 18800
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    const config = loadConfig(configPath);

    expect(config.telegram?.bot_token).toBe('env-token-value');

    delete process.env.TEST_BOT_TOKEN;
    rmSync(testDir, { recursive: true });
  });

  it('throws on invalid config', () => {
    mkdirSync(testDir, { recursive: true });
    const configPath = join(testDir, 'config.yaml');
    writeFileSync(configPath, `
telegram:
  bot_token: ""
`);

    expect(() => loadConfig(configPath)).toThrow();

    rmSync(testDir, { recursive: true });
  });

  it('overrides server.port from PORT env var when set', () => {
    mkdirSync(testDir, { recursive: true });
    const configPath = join(testDir, 'config.yaml');

    const prevPort = process.env.PORT;
    process.env.PORT = '3777';

    writeFileSync(configPath, `
telegram:
  bot_token: "test-token"
  allowed_chat_ids: [123]
server:
  port: 18800
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    const config = loadConfig(configPath);
    expect(config.server.port).toBe(3777);

    if (prevPort !== undefined) {
      process.env.PORT = prevPort;
    } else {
      delete process.env.PORT;
    }

    rmSync(testDir, { recursive: true });
  });

  it('ignores invalid PORT env var values', () => {
    mkdirSync(testDir, { recursive: true });
    const configPath = join(testDir, 'config.yaml');

    const prevPort = process.env.PORT;
    process.env.PORT = 'not-a-number';

    writeFileSync(configPath, `
telegram:
  bot_token: "test-token"
  allowed_chat_ids: [123]
server:
  port: 18800
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    const config = loadConfig(configPath);
    expect(config.server.port).toBe(18800);

    if (prevPort !== undefined) {
      process.env.PORT = prevPort;
    } else {
      delete process.env.PORT;
    }

    rmSync(testDir, { recursive: true });
  });
});

describe('loadConfig with overlay', () => {
  const testDir = join(tmpdir(), 'flynn-test-overlay');

  afterEach(() => {
    try { rmSync(testDir, { recursive: true }); } catch {}
    delete process.env.TEST_OVERLAY_TOKEN;
    delete process.env.TEST_OVERLAY_MODEL;
  });

  it('merges overlay on top of base config', () => {
    mkdirSync(testDir, { recursive: true });
    const basePath = join(testDir, 'config.yaml');
    const overlayPath = join(testDir, 'docker.yaml');

    writeFileSync(basePath, `
telegram:
  bot_token: "base-token"
  allowed_chat_ids: [123]
server:
  port: 18800
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    writeFileSync(overlayPath, `
server:
  port: 9999
`);

    const config = loadConfig(basePath, overlayPath);
    expect(config.server.port).toBe(9999);
    expect(config.telegram?.bot_token).toBe('base-token');
  });

  it('loads base-only when no overlay provided', () => {
    mkdirSync(testDir, { recursive: true });
    const basePath = join(testDir, 'config.yaml');

    writeFileSync(basePath, `
telegram:
  bot_token: "base-only-token"
  allowed_chat_ids: [456]
server:
  port: 18800
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    const config = loadConfig(basePath);
    expect(config.telegram?.bot_token).toBe('base-only-token');
    expect(config.server.port).toBe(18800);
  });

  it('expands env vars in both base and overlay', () => {
    mkdirSync(testDir, { recursive: true });
    const basePath = join(testDir, 'config.yaml');
    const overlayPath = join(testDir, 'docker.yaml');

    process.env.TEST_OVERLAY_TOKEN = 'expanded-token';
    process.env.TEST_OVERLAY_MODEL = 'gpt-4o';

    writeFileSync(basePath, `
telegram:
  bot_token: \${TEST_OVERLAY_TOKEN}
  allowed_chat_ids: [123]
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    writeFileSync(overlayPath, `
models:
  default:
    model: \${TEST_OVERLAY_MODEL}
`);

    const config = loadConfig(basePath, overlayPath);
    expect(config.telegram?.bot_token).toBe('expanded-token');
    expect(config.models.default.model).toBe('gpt-4o');
  });

  it('throws when overlay path provided but file does not exist', () => {
    mkdirSync(testDir, { recursive: true });
    const basePath = join(testDir, 'config.yaml');

    writeFileSync(basePath, `
telegram:
  bot_token: "token"
  allowed_chat_ids: [123]
models:
  default:
    provider: anthropic
    model: claude-sonnet
`);

    expect(() => loadConfig(basePath, '/nonexistent/overlay.yaml')).toThrow('/nonexistent/overlay.yaml');
  });
});
