import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
import express from 'express';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import type { Server } from 'http';
import accountRoutes from '../../../src/web-server/routes/account-routes';
import ProfileRegistry from '../../../src/auth/profile-registry';
import { InstanceManager } from '../../../src/management/instance-manager';

async function getJson<T>(baseUrl: string, routePath: string): Promise<T> {
  const response = await fetch(`${baseUrl}${routePath}`);
  expect(response.status).toBe(200);
  return (await response.json()) as T;
}

async function deletePath(baseUrl: string, routePath: string): Promise<Response> {
  return fetch(`${baseUrl}${routePath}`, { method: 'DELETE' });
}

async function putJson(baseUrl: string, routePath: string, body: unknown): Promise<Response> {
  return fetch(`${baseUrl}${routePath}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
}

describe('web-server account-routes context normalization', () => {
  let server: Server;
  let baseUrl = '';
  let tempHome = '';
  let originalCcsHome: string | undefined;
  let originalCcsUnified: string | undefined;

  beforeAll(async () => {
    const app = express();
    app.use(express.json());
    app.use('/api/accounts', accountRoutes);

    await new Promise<void>((resolve, reject) => {
      server = app.listen(0, '127.0.0.1');
      const handleError = (error: Error) => reject(error);
      server.once('error', handleError);
      server.once('listening', () => {
        server.off('error', handleError);
        resolve();
      });
    });

    const address = server.address();
    if (!address || typeof address === 'string') {
      throw new Error('Unable to resolve test server port');
    }
    baseUrl = `http://127.0.0.1:${address.port}`;
  });

  afterAll(async () => {
    await new Promise<void>((resolve) => server.close(() => resolve()));
  });

  beforeEach(() => {
    tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-account-routes-context-'));
    originalCcsHome = process.env.CCS_HOME;
    originalCcsUnified = process.env.CCS_UNIFIED_CONFIG;

    process.env.CCS_HOME = tempHome;
    process.env.CCS_UNIFIED_CONFIG = '1';
  });

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

    if (originalCcsUnified !== undefined) process.env.CCS_UNIFIED_CONFIG = originalCcsUnified;
    else delete process.env.CCS_UNIFIED_CONFIG;

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

  it('normalizes invalid persisted account context metadata in API response', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 8',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    last_used: null',
        '    context_mode: weird',
        '    context_group: "###"',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const payload = await getJson<{
      accounts: Array<{
        name: string;
        context_mode?: string;
        context_group?: string;
        continuity_mode?: string;
        context_inferred?: boolean;
      }>;
    }>(baseUrl, '/api/accounts');

    const work = payload.accounts.find((account) => account.name === 'work');
    expect(work).toBeTruthy();
    expect(work?.context_mode).toBe('isolated');
    expect(work?.context_inferred).toBe(true);
    expect(work && 'context_group' in work).toBe(false);
    expect(work && 'continuity_mode' in work).toBe(false);
  });

  it('falls back shared accounts with invalid groups to default shared group', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 8',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    last_used: null',
        '    context_mode: shared',
        '    context_group: "###"',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const payload = await getJson<{
      accounts: Array<{
        name: string;
        context_mode?: string;
        context_group?: string;
        continuity_mode?: string;
        context_inferred?: boolean;
        continuity_inferred?: boolean;
      }>;
    }>(baseUrl, '/api/accounts');

    const work = payload.accounts.find((account) => account.name === 'work');
    expect(work).toBeTruthy();
    expect(work?.context_mode).toBe('shared');
    expect(work?.context_group).toBe('default');
    expect(work?.continuity_mode).toBe('standard');
    expect(work?.context_inferred).toBe(false);
    expect(work?.continuity_inferred).toBe(true);
  });

  it('reports the plain ccs lane as native when no account default or inheritance exists', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 12',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const payload = await getJson<{
      plain_ccs_lane?: {
        kind: string;
        account_name?: string | null;
      };
    }>(baseUrl, '/api/accounts');

    expect(payload.plain_ccs_lane?.kind).toBe('native');
    expect(payload.plain_ccs_lane?.account_name).toBeNull();
  });

  it('reports the plain ccs lane as an account when default profile is an auth account', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 12',
        'default: work',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    context_mode: shared',
        '    context_group: default',
        '    continuity_mode: deeper',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const payload = await getJson<{
      plain_ccs_lane?: {
        kind: string;
        account_name?: string | null;
      };
    }>(baseUrl, '/api/accounts');

    expect(payload.plain_ccs_lane?.kind).toBe('account-default');
    expect(payload.plain_ccs_lane?.account_name).toBe('work');
  });

  it('does not initialize an account instance as a side effect of listing accounts', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 12',
        'default: work',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    context_mode: shared',
        '    context_group: default',
        '    continuity_mode: deeper',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const instancePath = path.join(ccsDir, 'instances', 'work');
    expect(fs.existsSync(instancePath)).toBe(false);

    await getJson(baseUrl, '/api/accounts');

    expect(fs.existsSync(instancePath)).toBe(false);
  });

  it('does not delete metadata when instance deletion fails', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });
    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 8',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    last_used: null',
        '    context_mode: shared',
        '    context_group: sprint-a',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );
    const registry = new ProfileRegistry();

    const originalDeleteInstance = InstanceManager.prototype.deleteInstance;
    InstanceManager.prototype.deleteInstance = () => {
      throw new Error('simulated instance delete failure');
    };

    try {
      const response = await deletePath(baseUrl, '/api/accounts/work');
      expect(response.status).toBe(500);
      expect(registry.hasAccountUnified('work')).toBe(true);
    } finally {
      InstanceManager.prototype.deleteInstance = originalDeleteInstance;
    }
  });

  it('updates existing account context metadata and normalizes shared group', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 8',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    last_used: null',
        '    context_mode: isolated',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const response = await putJson(baseUrl, '/api/accounts/work/context', {
      context_mode: 'shared',
      context_group: ' Team Alpha ',
      continuity_mode: 'deeper',
    });
    expect(response.status).toBe(200);
    const payload = (await response.json()) as {
      context_mode: string;
      context_group: string | null;
      continuity_mode?: string | null;
      context_inferred?: boolean;
      continuity_inferred?: boolean;
    };
    expect(payload.context_mode).toBe('shared');
    expect(payload.context_group).toBe('team-alpha');
    expect(payload.continuity_mode).toBe('deeper');
    expect(payload.context_inferred).toBe(false);
    expect(payload.continuity_inferred).toBe(false);

    const accountsPayload = await getJson<{
      accounts: Array<{
        name: string;
        context_mode?: string;
        context_group?: string;
        continuity_mode?: string;
      }>;
    }>(baseUrl, '/api/accounts');
    const work = accountsPayload.accounts.find((account) => account.name === 'work');
    expect(work?.context_mode).toBe('shared');
    expect(work?.context_group).toBe('team-alpha');
    expect(work?.continuity_mode).toBe('deeper');
  });

  it('rejects shared mode updates without context_group', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 8',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    last_used: null',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const response = await putJson(baseUrl, '/api/accounts/work/context', {
      context_mode: 'shared',
    });
    expect(response.status).toBe(400);

    const payload = (await response.json()) as { error: string };
    expect(payload.error).toContain('context_group');
  });

  it('rejects context updates for CLIProxy account identifiers', async () => {
    const response = await putJson(baseUrl, '/api/accounts/gemini:test/context', {
      context_mode: 'shared',
      context_group: 'default',
      continuity_mode: 'deeper',
    });
    expect(response.status).toBe(400);

    const payload = (await response.json()) as { error: string };
    expect(payload.error).toContain('CLIProxy');
  });

  it('rejects invalid continuity mode updates', async () => {
    const ccsDir = path.join(tempHome, '.ccs');
    fs.mkdirSync(ccsDir, { recursive: true });

    fs.writeFileSync(
      path.join(ccsDir, 'config.yaml'),
      [
        'version: 8',
        'accounts:',
        '  work:',
        '    created: "2026-02-01T00:00:00.000Z"',
        '    last_used: null',
        'profiles: {}',
        'cliproxy:',
        '  oauth_accounts: {}',
        '  providers: {}',
        '  variants: {}',
      ].join('\n'),
      'utf8'
    );

    const response = await putJson(baseUrl, '/api/accounts/work/context', {
      context_mode: 'shared',
      context_group: 'default',
      continuity_mode: 'extreme',
    });

    expect(response.status).toBe(400);
    const payload = (await response.json()) as { error: string };
    expect(payload.error).toContain('continuity_mode');
  });
});
