/**
 * Unit tests for composite variant service operations
 */

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import {
  createCompositeVariant,
  updateCompositeVariant,
} from '../../../src/cliproxy/services/variant-service';
import {
  saveCompositeVariantUnified,
  listVariantsFromConfig,
} from '../../../src/cliproxy/services/variant-config-adapter';
import { CompositeVariantConfig } from '../../../src/config/unified-config-types';

describe('updateCompositeVariant', () => {
  let tmpDir: string;
  let originalCcsDir: string | undefined;

  beforeEach(() => {
    // Create temp directory for isolated config
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-composite-test-'));
    originalCcsDir = process.env.CCS_DIR;
    // Use CCS_DIR (not CCS_HOME which appends .ccs)
    process.env.CCS_DIR = tmpDir;

    // Create unified config file
    const configDir = tmpDir;
    const configPath = path.join(configDir, 'config.yaml');
    fs.mkdirSync(configDir, { recursive: true });
    fs.writeFileSync(
      configPath,
      `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants: {}
`,
      'utf-8'
    );
  });

  afterEach(() => {
    // Restore original CCS_DIR
    if (originalCcsDir !== undefined) {
      process.env.CCS_DIR = originalCcsDir;
    } else {
      delete process.env.CCS_DIR;
    }

    // Clean up temp directory
    if (tmpDir && fs.existsSync(tmpDir)) {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    }
  });

  it('should update partial tier config (only opus model)', () => {
    // Setup: Create initial composite variant
    const initialConfig: CompositeVariantConfig = {
      type: 'composite',
      default_tier: 'sonnet',
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-3-pro-preview' },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      },
      settings: 'cliproxy/composite-test.settings.json',
      port: 8318,
    };
    saveCompositeVariantUnified('test', initialConfig);

    // Create dummy settings file to avoid deletion error
    const settingsDir = path.join(tmpDir, 'cliproxy');
    fs.mkdirSync(settingsDir, { recursive: true });
    fs.writeFileSync(
      path.join(settingsDir, 'composite-test.settings.json'),
      JSON.stringify({ env: {} }),
      'utf-8'
    );

    // Test: Update only opus tier
    const result = updateCompositeVariant('test', {
      tiers: {
        opus: { provider: 'agy', model: 'claude-opus-4-6-thinking' },
      },
    });

    expect(result.success).toBe(true);
    expect(result.variant?.tiers?.opus.model).toBe('claude-opus-4-6-thinking');
    expect(result.variant?.tiers?.sonnet.model).toBe('claude-sonnet-4-6'); // Unchanged
    expect(result.variant?.tiers?.haiku.model).toBe('claude-haiku-4-5-20251001'); // Unchanged
  });

  it('should update default tier', () => {
    // Setup: Create initial composite variant
    const initialConfig: CompositeVariantConfig = {
      type: 'composite',
      default_tier: 'sonnet',
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-3-pro-preview' },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      },
      settings: 'cliproxy/composite-test.settings.json',
      port: 8318,
    };
    saveCompositeVariantUnified('test', initialConfig);

    // Create dummy settings file
    const settingsDir = path.join(tmpDir, 'cliproxy');
    fs.mkdirSync(settingsDir, { recursive: true });
    fs.writeFileSync(
      path.join(settingsDir, 'composite-test.settings.json'),
      JSON.stringify({ env: {} }),
      'utf-8'
    );

    // Test: Update default tier
    const result = updateCompositeVariant('test', {
      defaultTier: 'opus',
    });

    expect(result.success).toBe(true);
    expect(result.variant?.default_tier).toBe('opus');
    expect(result.variant?.provider).toBe('gemini'); // Provider from opus tier
  });

  it('should update all tiers', () => {
    // Setup: Create initial composite variant
    const initialConfig: CompositeVariantConfig = {
      type: 'composite',
      default_tier: 'sonnet',
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-3-pro-preview' },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      },
      settings: 'cliproxy/composite-test.settings.json',
      port: 8318,
    };
    saveCompositeVariantUnified('test', initialConfig);

    // Create dummy settings file
    const settingsDir = path.join(tmpDir, 'cliproxy');
    fs.mkdirSync(settingsDir, { recursive: true });
    fs.writeFileSync(
      path.join(settingsDir, 'composite-test.settings.json'),
      JSON.stringify({ env: {} }),
      'utf-8'
    );

    // Test: Update all tiers
    const result = updateCompositeVariant('test', {
      tiers: {
        opus: { provider: 'agy', model: 'claude-opus-4-6-thinking' },
        sonnet: { provider: 'codex', model: 'codex-sonnet-4-5' },
        haiku: { provider: 'gemini', model: 'gemini-2.5-flash' },
      },
    });

    expect(result.success).toBe(true);
    expect(result.variant?.tiers?.opus.provider).toBe('agy');
    expect(result.variant?.tiers?.sonnet.provider).toBe('codex');
    expect(result.variant?.tiers?.haiku.provider).toBe('gemini');
  });

  it('should preserve optional tier fields when updating provider/model only', () => {
    const initialConfig: CompositeVariantConfig = {
      type: 'composite',
      default_tier: 'sonnet',
      tiers: {
        opus: {
          provider: 'agy',
          model: 'claude-opus-4-6-thinking',
          fallback: { provider: 'gemini', model: 'gemini-2.5-flash' },
          thinking: 'xhigh',
          account: 'team-a',
        },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      },
      settings: 'cliproxy/composite-test.settings.json',
      port: 8318,
    };
    saveCompositeVariantUnified('test', initialConfig);

    const settingsDir = path.join(tmpDir, 'cliproxy');
    fs.mkdirSync(settingsDir, { recursive: true });
    fs.writeFileSync(
      path.join(settingsDir, 'composite-test.settings.json'),
      JSON.stringify({ env: {} }),
      'utf-8'
    );

    const result = updateCompositeVariant('test', {
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-2.5-pro' },
      },
    });

    expect(result.success).toBe(true);
    expect(result.variant?.tiers?.opus.provider).toBe('gemini');
    expect(result.variant?.tiers?.opus.model).toBe('gemini-2.5-pro');
    expect(result.variant?.tiers?.opus.fallback).toEqual({
      provider: 'gemini',
      model: 'gemini-2.5-flash',
    });
    expect(result.variant?.tiers?.opus.thinking).toBe('xhigh');
    expect(result.variant?.tiers?.opus.account).toBe('team-a');
  });

  it('should preserve existing custom settings path and custom settings fields', () => {
    const customSettingsPath = path.join(tmpDir, 'custom', 'my-composite.settings.json');
    const initialConfig: CompositeVariantConfig = {
      type: 'composite',
      default_tier: 'sonnet',
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-3-pro-preview' },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      },
      settings: customSettingsPath,
      port: 8318,
    };
    saveCompositeVariantUnified('test', initialConfig);

    fs.mkdirSync(path.dirname(customSettingsPath), { recursive: true });
    fs.writeFileSync(
      customSettingsPath,
      JSON.stringify(
        {
          env: {
            ANTHROPIC_BASE_URL: 'http://127.0.0.1:8318',
            ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
            ANTHROPIC_MODEL: 'claude-sonnet-4-6',
            ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3-pro-preview',
            ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6',
            ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5-20251001',
            CUSTOM_ENV: 'preserve-this',
          },
          hooks: { PreToolUse: [{ matcher: 'WebSearch', hooks: [] }] },
          customPreset: true,
        },
        null,
        2
      ),
      'utf-8'
    );

    const result = updateCompositeVariant('test', {
      tiers: {
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6(high)' },
      },
    });

    expect(result.success).toBe(true);
    expect(result.variant?.settings).toBe(customSettingsPath);

    const updatedSettings = JSON.parse(fs.readFileSync(customSettingsPath, 'utf-8')) as {
      env: Record<string, string>;
      hooks: { PreToolUse: unknown[] };
      customPreset: boolean;
    };
    expect(updatedSettings.env.ANTHROPIC_MODEL).toBe('claude-sonnet-4-6(high)');
    expect(updatedSettings.env.CUSTOM_ENV).toBe('preserve-this');
    expect(updatedSettings.hooks.PreToolUse.length).toBe(1);
    expect(updatedSettings.customPreset).toBe(true);

    const variants = listVariantsFromConfig();
    expect(variants.test.settings).toBe(customSettingsPath);
  });

  it('should return error when variant does not exist', () => {
    const result = updateCompositeVariant('nonexistent', {
      tiers: {
        opus: { provider: 'agy', model: 'claude-opus-4-6-thinking' },
      },
    });

    expect(result.success).toBe(false);
    expect(result.error).toContain('not found');
  });

  it('should return error when variant is not composite type', () => {
    // Setup: Create non-composite variant in unified config
    const configPath = path.join(tmpDir, 'config.yaml');
    const yamlContent = `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants:
    simple:
      provider: gemini
      settings: cliproxy/simple.settings.json
      port: 8318
`;
    fs.writeFileSync(configPath, yamlContent, 'utf-8');

    const result = updateCompositeVariant('simple', {
      tiers: {
        opus: { provider: 'agy', model: 'claude-opus-4-6-thinking' },
      },
    });

    expect(result.success).toBe(false);
    expect(result.error).toContain('not a composite variant');
  });
});

describe('createCompositeVariant', () => {
  let tmpDir: string;
  let originalCcsDir: string | undefined;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-composite-test-'));
    originalCcsDir = process.env.CCS_DIR;
    process.env.CCS_DIR = tmpDir;

    const configPath = path.join(tmpDir, 'config.yaml');
    fs.writeFileSync(
      configPath,
      `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants: {}
`,
      'utf-8'
    );
  });

  afterEach(() => {
    if (originalCcsDir !== undefined) {
      process.env.CCS_DIR = originalCcsDir;
    } else {
      delete process.env.CCS_DIR;
    }

    if (tmpDir && fs.existsSync(tmpDir)) {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    }
  });

  it('returns validation error for missing required tier in create flow', () => {
    const result = createCompositeVariant({
      name: 'broken',
      defaultTier: 'sonnet',
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-2.5-pro' },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
      } as unknown as {
        opus: { provider: 'gemini' | 'codex' | 'agy'; model: string };
        sonnet: { provider: 'gemini' | 'codex' | 'agy'; model: string };
        haiku: { provider: 'gemini' | 'codex' | 'agy'; model: string };
      },
    });

    expect(result.success).toBe(false);
    expect(result.error).toContain("Missing required tier 'haiku'");
  });

  it('returns validation error for null tier payload in create flow', () => {
    const result = createCompositeVariant({
      name: 'broken-null',
      defaultTier: 'sonnet',
      tiers: {
        opus: null,
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      } as unknown as {
        opus: { provider: 'gemini' | 'codex' | 'agy'; model: string };
        sonnet: { provider: 'gemini' | 'codex' | 'agy'; model: string };
        haiku: { provider: 'gemini' | 'codex' | 'agy'; model: string };
      },
    });

    expect(result.success).toBe(false);
    expect(result.error).toContain("Invalid tier config for 'opus'");
  });

  it('stores composite settings path under active CCS_DIR', () => {
    const result = createCompositeVariant({
      name: 'scoped',
      defaultTier: 'sonnet',
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-2.5-pro' },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      },
    });

    expect(result.success).toBe(true);

    const variants = listVariantsFromConfig();
    const expectedPath = path.join(tmpDir, 'composite-scoped.settings.json');
    expect(variants.scoped.settings).toBe(expectedPath);
    expect(result.settingsPath).toBe(expectedPath);
    expect(fs.existsSync(expectedPath)).toBe(true);
  });
});

describe('saveCompositeVariantUnified', () => {
  let tmpDir: string;
  let originalCcsDir: string | undefined;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-composite-test-'));
    originalCcsDir = process.env.CCS_DIR;
    process.env.CCS_DIR = tmpDir;

    // Create unified config file
    const configPath = path.join(tmpDir, 'config.yaml');
    fs.writeFileSync(
      configPath,
      `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants: {}
`,
      'utf-8'
    );
  });

  afterEach(() => {
    if (originalCcsDir !== undefined) {
      process.env.CCS_DIR = originalCcsDir;
    } else {
      delete process.env.CCS_DIR;
    }

    if (tmpDir && fs.existsSync(tmpDir)) {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    }
  });

  it('should save composite variant to unified config', () => {
    const config: CompositeVariantConfig = {
      type: 'composite',
      default_tier: 'sonnet',
      tiers: {
        opus: { provider: 'gemini', model: 'gemini-3-pro-preview' },
        sonnet: { provider: 'agy', model: 'claude-sonnet-4-6' },
        haiku: { provider: 'agy', model: 'claude-haiku-4-5-20251001' },
      },
      settings: 'cliproxy/composite-test.settings.json',
      port: 8318,
    };

    saveCompositeVariantUnified('test', config);

    const variants = listVariantsFromConfig();
    expect(variants.test).toBeDefined();
    expect(variants.test.type).toBe('composite');
    expect(variants.test.default_tier).toBe('sonnet');
    expect(variants.test.tiers?.opus.model).toBe('gemini-3-pro-preview');
  });
});

describe('listVariantsFromConfig - composite variants', () => {
  let tmpDir: string;
  let originalCcsDir: string | undefined;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-composite-test-'));
    originalCcsDir = process.env.CCS_DIR;
    process.env.CCS_DIR = tmpDir;
  });

  afterEach(() => {
    if (originalCcsDir !== undefined) {
      process.env.CCS_DIR = originalCcsDir;
    } else {
      delete process.env.CCS_DIR;
    }

    if (tmpDir && fs.existsSync(tmpDir)) {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    }
  });

  it('should list composite variants with fallback field', () => {
    // Create unified config with composite variant that has fallback
    const configPath = path.join(tmpDir, 'config.yaml');
    const yamlContent = `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants:
    test:
      type: composite
      default_tier: sonnet
      tiers:
        opus:
          provider: agy
          model: claude-opus-4-6-thinking
          fallback:
            provider: gemini
            model: gemini-3-pro-preview
        sonnet:
          provider: agy
          model: claude-sonnet-4-6
        haiku:
          provider: agy
          model: claude-haiku-4-5-20251001
      settings: cliproxy/composite-test.settings.json
      port: 8318
`;
    fs.writeFileSync(configPath, yamlContent, 'utf-8');

    const variants = listVariantsFromConfig();
    expect(variants.test).toBeDefined();
    expect(variants.test.hasFallback).toBe(true);
  });

  it('should list composite variants with thinking field', () => {
    // Create unified config with composite variant that has per-tier thinking
    const configPath = path.join(tmpDir, 'config.yaml');
    const yamlContent = `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants:
    test:
      type: composite
      default_tier: sonnet
      tiers:
        opus:
          provider: agy
          model: claude-opus-4-6-thinking
          thinking: xhigh
        sonnet:
          provider: agy
          model: claude-sonnet-4-6
          thinking: medium
        haiku:
          provider: agy
          model: claude-haiku-4-5-20251001
          thinking: off
      settings: cliproxy/composite-test.settings.json
      port: 8318
`;
    fs.writeFileSync(configPath, yamlContent, 'utf-8');

    const variants = listVariantsFromConfig();
    expect(variants.test).toBeDefined();
    expect(variants.test.tiers?.opus.thinking).toBe('xhigh');
    expect(variants.test.tiers?.sonnet.thinking).toBe('medium');
    expect(variants.test.tiers?.haiku.thinking).toBe('off');
  });

  it('should have hasFallback=false when no fallbacks configured', () => {
    // Create unified config with composite variant WITHOUT fallback
    const configPath = path.join(tmpDir, 'config.yaml');
    const yamlContent = `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants:
    test:
      type: composite
      default_tier: sonnet
      tiers:
        opus:
          provider: agy
          model: claude-opus-4-6-thinking
        sonnet:
          provider: agy
          model: claude-sonnet-4-6
        haiku:
          provider: agy
          model: claude-haiku-4-5-20251001
      settings: cliproxy/composite-test.settings.json
      port: 8318
`;
    fs.writeFileSync(configPath, yamlContent, 'utf-8');

    const variants = listVariantsFromConfig();
    expect(variants.test).toBeDefined();
    expect(variants.test.hasFallback).toBe(false);
  });

  it('should skip malformed composite variant and keep valid variants', () => {
    const configPath = path.join(tmpDir, 'config.yaml');
    const yamlContent = `version: 2
accounts: {}
profiles: {}
preferences:
  theme: system
  telemetry: false
  auto_update: true
cliproxy:
  oauth_accounts: {}
  providers:
    - gemini
    - codex
    - agy
  variants:
    bad:
      type: composite
      default_tier: sonnet
      tiers:
        opus:
          provider: agy
          model: claude-opus-4-6-thinking
        sonnet:
          provider: agy
          model: claude-sonnet-4-6
      settings: cliproxy/bad.settings.json
      port: 8318
    good:
      provider: gemini
      settings: cliproxy/good.settings.json
      port: 8319
`;
    fs.writeFileSync(configPath, yamlContent, 'utf-8');

    const variants = listVariantsFromConfig();
    expect(variants.bad).toBeUndefined();
    expect(variants.good).toBeDefined();
    expect(variants.good.provider).toBe('gemini');
  });
});
