import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import {
  loadMigrationCheckData,
  migrate,
  resolveManagedBackupPath,
  rollback,
} from '../../../src/config/migration-manager';
import { loadUnifiedConfig, saveUnifiedConfig } from '../../../src/config/unified-config-loader';
import { createEmptyUnifiedConfig } from '../../../src/config/unified-config-types';

describe('migration-manager legacy kimi compatibility', () => {
  let tempHome: string;
  let ccsDir: string;
  let originalCcsHome: string | undefined;

  beforeEach(() => {
    tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-migration-manager-test-'));
    originalCcsHome = process.env.CCS_HOME;
    process.env.CCS_HOME = tempHome;
    ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });
  });

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

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

  it('prefers explicit canonical km profile over legacy kimi when both exist', async () => {
    const kmSettingsPath = path.join(ccsDir, 'km.settings.json');
    const kimiSettingsPath = path.join(ccsDir, 'kimi.settings.json');

    fs.writeFileSync(kmSettingsPath, JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-km' } }));
    fs.writeFileSync(
      kimiSettingsPath,
      JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-kimi' } })
    );

    // Intentionally place legacy alias first to verify deterministic behavior.
    fs.writeFileSync(
      path.join(ccsDir, 'config.json'),
      JSON.stringify(
        {
          profiles: {
            kimi: kimiSettingsPath,
            km: kmSettingsPath,
          },
        },
        null,
        2
      )
    );

    const result = await migrate(true);

    expect(result.success).toBe(true);
    expect(
      result.migratedFiles.some((entry) =>
        entry.includes(
          `config.json.profiles.km → config.yaml.profiles.km (settings: ${kmSettingsPath})`
        )
      )
    ).toBe(true);
    expect(
      result.migratedFiles.some((entry) => entry.includes(`(settings: ${kimiSettingsPath})`))
    ).toBe(false);
    expect(
      result.warnings.some((warning) =>
        warning.includes(
          'Skipped kimi: canonical profile "km" exists in config.json with different settings'
        )
      )
    ).toBe(true);
  });

  it('renames case-variant legacy Kimi profile key to km', async () => {
    const kimiSettingsPath = path.join(ccsDir, 'kimi.settings.json');
    fs.writeFileSync(
      kimiSettingsPath,
      JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-kimi-case-variant' } })
    );

    fs.writeFileSync(
      path.join(ccsDir, 'config.json'),
      JSON.stringify(
        {
          profiles: {
            Kimi: kimiSettingsPath,
          },
        },
        null,
        2
      )
    );

    const result = await migrate(true);

    expect(result.success).toBe(true);
    expect(
      result.migratedFiles.some((entry) =>
        entry.includes('config.json.profiles.Kimi → config.yaml.profiles.km')
      )
    ).toBe(true);
  });

  it('treats legacy kimi profile as migrated when unified config already has km', () => {
    const unifiedConfig = createEmptyUnifiedConfig();
    unifiedConfig.profiles.km = {
      type: 'api',
      settings: '~/.ccs/km.settings.json',
    };
    saveUnifiedConfig(unifiedConfig);

    fs.writeFileSync(
      path.join(ccsDir, 'config.json'),
      JSON.stringify(
        {
          profiles: {
            kimi: '~/.ccs/kimi.settings.json',
          },
        },
        null,
        2
      )
    );

    const checkData = loadMigrationCheckData();
    expect(checkData.needsMigration).toBe(false);
  });

  it('migrates account context metadata from profiles.json', async () => {
    fs.writeFileSync(
      path.join(ccsDir, 'profiles.json'),
      JSON.stringify(
        {
          default: 'work',
          profiles: {
            work: {
              type: 'account',
              created: '2026-02-01T00:00:00.000Z',
              last_used: null,
              context_mode: 'shared',
              context_group: 'sprint-a',
            },
            personal: {
              type: 'account',
              created: '2026-02-02T00:00:00.000Z',
              last_used: null,
            },
          },
        },
        null,
        2
      )
    );

    const result = await migrate(false);
    expect(result.success).toBe(true);

    const unified = loadUnifiedConfig();
    expect(unified).toBeTruthy();
    expect(unified?.accounts.work.context_mode).toBe('shared');
    expect(unified?.accounts.work.context_group).toBe('sprint-a');
    expect(unified?.accounts.work.continuity_mode).toBe('standard');
    expect(unified?.accounts.personal.context_mode).toBe('isolated');
    expect(unified?.accounts.personal.context_group).toBeUndefined();
    expect(unified?.accounts.personal.continuity_mode).toBeUndefined();
  });

  it('applies safe browser defaults when migrating legacy config files', async () => {
    fs.writeFileSync(
      path.join(ccsDir, 'config.json'),
      JSON.stringify(
        {
          profiles: {
            glm: '~/.ccs/glm.settings.json',
          },
        },
        null,
        2
      )
    );
    fs.writeFileSync(path.join(ccsDir, 'glm.settings.json'), JSON.stringify({ env: {} }));

    const result = await migrate(false);
    expect(result.success).toBe(true);

    const unified = loadUnifiedConfig();
    expect(unified?.browser).toEqual({
      claude: {
        enabled: false,
        policy: 'manual',
        user_data_dir: '',
        devtools_port: 9222,
        eval_mode: 'readonly',
      },
      codex: {
        enabled: false,
        policy: 'manual',
        eval_mode: 'readonly',
      },
    });
  });

  it('normalizes valid legacy shared groups and drops invalid ones during migration', async () => {
    fs.writeFileSync(
      path.join(ccsDir, 'profiles.json'),
      JSON.stringify(
        {
          default: 'work',
          profiles: {
            work: {
              type: 'account',
              created: '2026-02-01T00:00:00.000Z',
              last_used: null,
              context_mode: 'shared',
              context_group: 'Sprint-A',
            },
            broken: {
              type: 'account',
              created: '2026-02-02T00:00:00.000Z',
              last_used: null,
              context_mode: 'shared',
              context_group: '###',
            },
          },
        },
        null,
        2
      )
    );

    const result = await migrate(false);
    expect(result.success).toBe(true);
    expect(
      result.warnings.some((warning) =>
        warning.includes('Skipped invalid context group for account "broken"')
      )
    ).toBe(true);

    const unified = loadUnifiedConfig();
    expect(unified?.accounts.work.context_group).toBe('sprint-a');
    expect(unified?.accounts.broken.context_mode).toBe('shared');
    expect(unified?.accounts.broken.context_group).toBeUndefined();
    expect(unified?.accounts.work.continuity_mode).toBe('standard');
    expect(unified?.accounts.broken.continuity_mode).toBe('standard');
  });

  it('resolves only direct managed backup directories under ~/.ccs', () => {
    const managedBackupPath = path.join(ccsDir, 'backup-v1-2026-03-24');
    const externalBackupPath = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-backup-external-'));
    const symlinkedBackupPath = path.join(ccsDir, 'backup-v1-symlink');

    fs.mkdirSync(managedBackupPath, { recursive: true });
    fs.symlinkSync(externalBackupPath, symlinkedBackupPath, 'dir');

    expect(resolveManagedBackupPath(managedBackupPath)).toBe(
      fs.realpathSync.native(managedBackupPath)
    );
    expect(resolveManagedBackupPath(externalBackupPath)).toBeNull();
    expect(resolveManagedBackupPath(symlinkedBackupPath)).toBeNull();

    fs.rmSync(externalBackupPath, { recursive: true, force: true });
  });

  it('rejects rollback from directories outside managed CCS backups', async () => {
    const externalBackupPath = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-backup-invalid-'));

    expect(await rollback(externalBackupPath)).toBe(false);

    fs.rmSync(externalBackupPath, { recursive: true, force: true });
  });

  it('restores files only from managed CCS backup directories', async () => {
    const backupPath = path.join(ccsDir, 'backup-v1-2026-03-24');
    fs.mkdirSync(backupPath, { recursive: true });
    fs.writeFileSync(path.join(backupPath, 'config.json'), JSON.stringify({ restored: true }));
    fs.writeFileSync(path.join(ccsDir, 'config.yaml'), 'version: 8\n');
    fs.mkdirSync(path.join(ccsDir, 'cache'), { recursive: true });
    fs.writeFileSync(path.join(ccsDir, 'cache', 'usage.json'), '{}');

    const success = await rollback(backupPath);

    expect(success).toBe(true);
    expect(fs.existsSync(path.join(ccsDir, 'config.yaml'))).toBe(false);
    expect(JSON.parse(fs.readFileSync(path.join(ccsDir, 'config.json'), 'utf8'))).toEqual({
      restored: true,
    });
    expect(fs.existsSync(path.join(ccsDir, 'usage-cache.json'))).toBe(true);
  });
});
