import * as http from 'http';
import { afterEach, describe, expect, it } from 'bun:test';
import {
  buildCodexModelEffortMap,
  CodexReasoningProxy,
  getEffortForModel,
} from '../../../src/cliproxy/codex-reasoning-proxy';
import {
  parseEnvThinkingOverride,
  resolveRuntimeThinkingOverride,
  shouldDisableCodexReasoning,
} from '../../../src/cliproxy/executor/thinking-override-resolver';

type JsonRecord = Record<string, unknown>;

function closeServer(server: http.Server): Promise<void> {
  return new Promise((resolve) => {
    server.close(() => resolve());
  });
}

function listenOnRandomPort(server: http.Server): Promise<number> {
  return new Promise((resolve, reject) => {
    server.once('error', reject);
    server.listen(0, '127.0.0.1', () => {
      const address = server.address();
      if (typeof address !== 'object' || !address) {
        reject(new Error('Failed to resolve server address'));
        return;
      }
      resolve(address.port);
    });
  });
}

function postJson(
  url: string,
  body: JsonRecord
): Promise<{ statusCode: number; body: JsonRecord }> {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const payload = JSON.stringify(body);

    const req = http.request(
      {
        hostname: parsed.hostname,
        port: parsed.port,
        path: parsed.pathname + parsed.search,
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': Buffer.byteLength(payload),
        },
      },
      (res) => {
        let responseBody = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
          responseBody += chunk;
        });
        res.on('end', () => {
          let parsedResponse: JsonRecord = {};
          try {
            parsedResponse = responseBody ? (JSON.parse(responseBody) as JsonRecord) : {};
          } catch {
            parsedResponse = {};
          }
          resolve({ statusCode: res.statusCode ?? 0, body: parsedResponse });
        });
      }
    );

    req.on('error', reject);
    req.write(payload);
    req.end();
  });
}

describe('CodexReasoningProxy extended-context compatibility', () => {
  const cleanupServers: http.Server[] = [];

  afterEach(async () => {
    while (cleanupServers.length > 0) {
      const server = cleanupServers.pop();
      if (server) {
        await closeServer(server);
      }
    }
  });

  it('normalizes [1m] suffixes in effort map lookups', () => {
    const map = buildCodexModelEffortMap({
      defaultModel: 'gpt-5.3-codex-xhigh[1m]',
      sonnetModel: 'gpt-5.3-codex-high[1m]',
      haikuModel: 'gpt-5-mini-medium[1m]',
    });

    expect(getEffortForModel('gpt-5.3-codex-high', map, 'medium')).toBe('high');
    expect(getEffortForModel('gpt-5-mini-medium', map, 'high')).toBe('medium');
  });

  it('strips [1m] and codex effort suffixes before forwarding upstream', async () => {
    let capturedBody: JsonRecord | null = null;
    let capturedPath = '';

    const upstream = http.createServer((req, res) => {
      let rawBody = '';
      req.setEncoding('utf8');
      req.on('data', (chunk) => {
        rawBody += chunk;
      });
      req.on('end', () => {
        capturedPath = req.url || '';
        capturedBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ ok: true }));
      });
    });
    cleanupServers.push(upstream);

    const upstreamPort = await listenOnRandomPort(upstream);
    const proxy = new CodexReasoningProxy({
      upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
      modelMap: {
        defaultModel: 'gpt-5.3-codex-xhigh[1m]',
        opusModel: 'gpt-5.3-codex-xhigh[1m]',
        sonnetModel: 'gpt-5.3-codex-high[1m]',
        haikuModel: 'gpt-5-mini-medium[1m]',
      },
      defaultEffort: 'medium',
    });

    const proxyPort = await proxy.start();
    const response = await postJson(
      `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
      {
        model: 'gpt-5.3-codex-high[1m]',
        messages: [],
      }
    );

    proxy.stop();

    expect(response.statusCode).toBe(200);
    expect(capturedPath).toBe('/api/provider/codex/v1/messages');
    expect(capturedBody?.model).toBe('gpt-5.3-codex');
    expect((capturedBody?.reasoning as JsonRecord | undefined)?.effort).toBe('high');
  });

  it('skips reasoning injection when disableEffort is enabled', async () => {
    let capturedBody: JsonRecord | null = null;

    const upstream = http.createServer((req, res) => {
      let rawBody = '';
      req.setEncoding('utf8');
      req.on('data', (chunk) => {
        rawBody += chunk;
      });
      req.on('end', () => {
        capturedBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ ok: true }));
      });
    });
    cleanupServers.push(upstream);

    const upstreamPort = await listenOnRandomPort(upstream);
    const proxy = new CodexReasoningProxy({
      upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
      modelMap: {
        sonnetModel: 'gpt-5.3-codex-high',
      },
      disableEffort: true,
    });

    const proxyPort = await proxy.start();
    const response = await postJson(
      `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
      {
        model: 'gpt-5.3-codex-high',
        messages: [],
      }
    );

    proxy.stop();

    expect(response.statusCode).toBe(200);
    expect(capturedBody?.model).toBe('gpt-5.3-codex');
    expect((capturedBody?.reasoning as JsonRecord | undefined)?.effort).toBeUndefined();
  });

  it('does not strip unknown model ids that merely end with "-high"', async () => {
    let capturedBody: JsonRecord | null = null;

    const upstream = http.createServer((req, res) => {
      let rawBody = '';
      req.setEncoding('utf8');
      req.on('data', (chunk) => {
        rawBody += chunk;
      });
      req.on('end', () => {
        capturedBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ ok: true }));
      });
    });
    cleanupServers.push(upstream);

    const upstreamPort = await listenOnRandomPort(upstream);
    const proxy = new CodexReasoningProxy({
      upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
      modelMap: {
        defaultModel: 'gpt-5.1-codex-mini',
      },
      defaultEffort: 'medium',
    });

    const proxyPort = await proxy.start();
    const response = await postJson(
      `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
      {
        model: 'enterprise-internal-high',
        messages: [],
      }
    );

    proxy.stop();

    expect(response.statusCode).toBe(200);
    expect(capturedBody?.model).toBe('enterprise-internal-high');
  });

  it('retries unsupported live-session models once and remembers the fallback', async () => {
    const capturedModels: string[] = [];
    const capturedEfforts: Array<string | undefined> = [];

    const upstream = http.createServer((req, res) => {
      let rawBody = '';
      req.setEncoding('utf8');
      req.on('data', (chunk) => {
        rawBody += chunk;
      });
      req.on('end', () => {
        const requestBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
        const reasoning = requestBody.reasoning as JsonRecord | undefined;
        const model = String(requestBody.model ?? '');
        const effort = typeof reasoning?.effort === 'string' ? reasoning.effort : undefined;

        capturedModels.push(model);
        capturedEfforts.push(effort);

        if (model === 'gpt-5.4') {
          res.writeHead(400, { 'Content-Type': 'application/json' });
          res.end(
            JSON.stringify({
              error: {
                message: 'The requested model is not supported.',
                code: 'model_not_supported',
                param: 'model',
                type: 'invalid_request_error',
              },
            })
          );
          return;
        }

        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(
          JSON.stringify({
            ok: true,
            model,
            effort: effort ?? null,
          })
        );
      });
    });
    cleanupServers.push(upstream);

    const upstreamPort = await listenOnRandomPort(upstream);
    const proxy = new CodexReasoningProxy({
      upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
      modelMap: {
        defaultModel: 'gpt-5.4',
        haikuModel: 'gpt-5-codex-mini',
      },
      defaultEffort: 'medium',
    });

    const proxyPort = await proxy.start();
    const firstResponse = await postJson(
      `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
      {
        model: 'gpt-5.4-xhigh',
        messages: [],
      }
    );
    const secondResponse = await postJson(
      `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
      {
        model: 'gpt-5.4-xhigh',
        messages: [],
      }
    );

    proxy.stop();

    expect(firstResponse.statusCode).toBe(200);
    expect(secondResponse.statusCode).toBe(200);
    expect(firstResponse.body.model).toBe('gpt-5.4-mini');
    expect(firstResponse.body.effort).toBe('high');
    expect(secondResponse.body.model).toBe('gpt-5.4-mini');
    expect(secondResponse.body.effort).toBe('high');
    expect(capturedModels).toEqual(['gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-mini']);
    expect(capturedEfforts).toEqual(['xhigh', 'high', 'high']);
  });

  it('keeps reasoning enabled when CCS_THINKING=high overrides config off', async () => {
    let capturedBody: JsonRecord | null = null;

    expect(parseEnvThinkingOverride('high')).toBe('high');
    const { thinkingOverride } = resolveRuntimeThinkingOverride(undefined, 'high');
    const disableEffort = shouldDisableCodexReasoning(
      {
        mode: 'off',
        tier_defaults: {
          opus: 'high',
          sonnet: 'medium',
          haiku: 'low',
        },
        show_warnings: true,
      },
      thinkingOverride
    );

    const upstream = http.createServer((req, res) => {
      let rawBody = '';
      req.setEncoding('utf8');
      req.on('data', (chunk) => {
        rawBody += chunk;
      });
      req.on('end', () => {
        capturedBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ ok: true }));
      });
    });
    cleanupServers.push(upstream);

    const upstreamPort = await listenOnRandomPort(upstream);
    const proxy = new CodexReasoningProxy({
      upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
      disableEffort,
      defaultEffort: 'medium',
      modelMap: {
        defaultModel: 'gpt-5.3-codex',
      },
    });

    const proxyPort = await proxy.start();
    const response = await postJson(
      `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
      {
        model: 'gpt-5.3-codex-high',
        messages: [],
      }
    );

    proxy.stop();

    expect(response.statusCode).toBe(200);
    expect(disableEffort).toBe(false);
    expect(capturedBody?.model).toBe('gpt-5.3-codex');
    expect((capturedBody?.reasoning as JsonRecord | undefined)?.effort).toBe('high');
  });

  it('disables reasoning when CCS_THINKING=off is provided', async () => {
    let capturedBody: JsonRecord | null = null;

    expect(parseEnvThinkingOverride('off')).toBe('off');
    const { thinkingOverride } = resolveRuntimeThinkingOverride(undefined, 'off');
    const disableEffort = shouldDisableCodexReasoning(
      {
        mode: 'auto',
        tier_defaults: {
          opus: 'high',
          sonnet: 'medium',
          haiku: 'low',
        },
        show_warnings: true,
      },
      thinkingOverride
    );

    const upstream = http.createServer((req, res) => {
      let rawBody = '';
      req.setEncoding('utf8');
      req.on('data', (chunk) => {
        rawBody += chunk;
      });
      req.on('end', () => {
        capturedBody = rawBody ? (JSON.parse(rawBody) as JsonRecord) : {};
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ ok: true }));
      });
    });
    cleanupServers.push(upstream);

    const upstreamPort = await listenOnRandomPort(upstream);
    const proxy = new CodexReasoningProxy({
      upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`,
      disableEffort,
      defaultEffort: 'medium',
      modelMap: {
        defaultModel: 'gpt-5.3-codex',
      },
    });

    const proxyPort = await proxy.start();
    const response = await postJson(
      `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`,
      {
        model: 'gpt-5.3-codex-high',
        messages: [],
      }
    );

    proxy.stop();

    expect(response.statusCode).toBe(200);
    expect(disableEffort).toBe(true);
    expect(capturedBody?.model).toBe('gpt-5.3-codex');
    expect((capturedBody?.reasoning as JsonRecord | undefined)?.effort).toBeUndefined();
  });
});
