import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import * as smartai from '../ts/index.js';
import type { IOpenAiChatGptTokenData } from '../ts/index.js';
import {
  inspectOpenAiChatGptAuthSources,
  normalizeOpenAiChatGptAuth,
  resolveOpenAiChatGptAuth,
  writeOpenAiChatGptAuthFile,
} from '../ts_openai_chatgpt_auth/index.js';

interface IMockFetchRequest {
  url: string;
  init?: RequestInit;
}

function createJwt(payload: Record<string, unknown>): string {
  const encode = (value: Record<string, unknown>) => Buffer.from(JSON.stringify(value)).toString('base64url');
  return `${encode({ alg: 'none', typ: 'JWT' })}.${encode(payload)}.sig`;
}

function createTokenData(accountId = 'workspace-1', expiresAtSeconds = 4_102_444_800): IOpenAiChatGptTokenData {
  const accessToken = createJwt({
    email: 'user@example.com',
    exp: expiresAtSeconds,
    'https://api.openai.com/auth': {
      chatgpt_plan_type: 'pro',
      chatgpt_user_id: 'user-1',
      chatgpt_account_id: accountId,
      chatgpt_account_is_fedramp: false,
    },
  });
  const tokenInfo = smartai.parseOpenAiChatGptTokenInfo(accessToken);
  return {
    accessToken,
    refreshToken: 'refresh-token',
    accountId,
    tokenInfo,
  };
}

function jsonResponse(body: unknown, status = 200): Response {
  return new Response(JSON.stringify(body), {
    status,
    headers: { 'content-type': 'application/json' },
  });
}

function getHeader(init: RequestInit | undefined, name: string): string | null {
  return new Headers(init?.headers).get(name);
}

tap.test('requestOpenAiChatGptDeviceCode requests a user code', async () => {
  const originalFetch = globalThis.fetch;
  const requests: IMockFetchRequest[] = [];

  globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
    requests.push({ url: String(input), init });
    return jsonResponse({
      device_auth_id: 'device-1',
      usercode: 'ABCD-EFGH',
      interval: '2',
    });
  };

  try {
    const deviceCode = await smartai.requestOpenAiChatGptDeviceCode({
      issuer: 'https://auth.example.test',
      clientId: 'client-1',
    });

    expect(deviceCode).toEqual({
      verificationUrl: 'https://auth.example.test/codex/device',
      userCode: 'ABCD-EFGH',
      deviceAuthId: 'device-1',
      intervalSeconds: 2,
    });
    expect(requests[0].url).toEqual('https://auth.example.test/api/accounts/deviceauth/usercode');
    expect(JSON.parse(String(requests[0].init?.body))).toEqual({ client_id: 'client-1' });
  } finally {
    globalThis.fetch = originalFetch;
  }
});

tap.test('completeOpenAiChatGptDeviceCodeLogin polls and exchanges OAuth tokens', async () => {
  const originalFetch = globalThis.fetch;
  const requests: IMockFetchRequest[] = [];
  const tokenData = createTokenData('workspace-1');
  const responses = [
    jsonResponse({}, 403),
    jsonResponse({
      authorization_code: 'auth-code',
      code_challenge: 'challenge',
      code_verifier: 'verifier',
    }),
    jsonResponse({
      access_token: tokenData.accessToken,
      refresh_token: tokenData.refreshToken,
    }),
  ];

  globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
    requests.push({ url: String(input), init });
    const response = responses.shift();
    if (!response) throw new Error('Unexpected fetch call');
    return response;
  };

  try {
    const result = await smartai.completeOpenAiChatGptDeviceCodeLogin({
      verificationUrl: 'https://auth.example.test/codex/device',
      userCode: 'ABCD-EFGH',
      deviceAuthId: 'device-1',
      intervalSeconds: 1,
    }, {
      issuer: 'https://auth.example.test',
      clientId: 'client-1',
      forcedChatGptWorkspaceId: 'workspace-1',
      sleep: async () => undefined,
    });

    expect(result.accessToken).toEqual(tokenData.accessToken);
    expect(result.refreshToken).toEqual('refresh-token');
    expect(result.tokenInfo.chatgptAccountId).toEqual('workspace-1');
    expect(requests.length).toEqual(3);
    expect(JSON.parse(String(requests[0].init?.body))).toEqual({
      device_auth_id: 'device-1',
      user_code: 'ABCD-EFGH',
    });
    const tokenExchangeBody = new URLSearchParams(String(requests[2].init?.body));
    expect(tokenExchangeBody.get('grant_type')).toEqual('authorization_code');
    expect(tokenExchangeBody.get('code')).toEqual('auth-code');
    expect(tokenExchangeBody.get('redirect_uri')).toEqual('https://auth.example.test/deviceauth/callback');
    expect(tokenExchangeBody.get('client_id')).toEqual('client-1');
    expect(tokenExchangeBody.get('code_verifier')).toEqual('verifier');
  } finally {
    globalThis.fetch = originalFetch;
  }
});

tap.test('refreshOpenAiChatGptTokenData refreshes and preserves omitted token fields', async () => {
  const originalFetch = globalThis.fetch;
  const requests: IMockFetchRequest[] = [];
  const tokenData = createTokenData('workspace-1');
  const refreshedToken = createTokenData('workspace-1', 4_102_445_000).accessToken;

  globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
    requests.push({ url: String(input), init });
    return jsonResponse({ access_token: refreshedToken });
  };

  try {
    const result = await smartai.refreshOpenAiChatGptTokenData(tokenData, {
      issuer: 'https://auth.example.test',
      clientId: 'client-1',
    });

    expect(result.accessToken).toEqual(refreshedToken);
    expect(result.refreshToken).toEqual('refresh-token');
    expect(JSON.parse(String(requests[0].init?.body))).toEqual({
      client_id: 'client-1',
      grant_type: 'refresh_token',
      refresh_token: 'refresh-token',
    });
  } finally {
    globalThis.fetch = originalFetch;
  }
});

tap.test('getModel uses ChatGPT Codex backend for OpenAI ChatGPT auth', async () => {
  const originalFetch = globalThis.fetch;
  let capturedRequest: IMockFetchRequest | undefined;
  const tokenData = createTokenData('workspace-1');
  const model = smartai.getModel({
    provider: 'openai',
    model: 'gpt-5.5',
    openAiChatGptAuth: tokenData,
  });

  globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
    capturedRequest = { url: String(input), init };
    return jsonResponse({
      id: 'resp-1',
      created_at: 1,
      model: 'gpt-5.5',
      output: [{
        type: 'message',
        role: 'assistant',
        id: 'msg-1',
        content: [{ type: 'output_text', text: 'ok', annotations: [] }],
      }],
      usage: {
        input_tokens: 1,
        output_tokens: 1,
      },
    });
  };

  try {
    await smartai.generateText({
      model,
      system: 'system prompt',
      prompt: 'hello',
      providerOptions: {
        openai: {
          promptCacheKey: 'session-1',
          promptCacheRetention: 'in_memory',
        },
      },
    });

    expect(capturedRequest?.url).toEqual('https://chatgpt.com/backend-api/codex/responses');
    expect(getHeader(capturedRequest?.init, 'authorization')).toEqual(`Bearer ${tokenData.accessToken}`);
    expect(getHeader(capturedRequest?.init, 'chatgpt-account-id')).toEqual('workspace-1');
    expect(getHeader(capturedRequest?.init, 'originator')).toEqual('smartai');
    const capturedBody = JSON.parse(String(capturedRequest?.init?.body));
    expect(capturedBody.instructions).toEqual('system prompt');
    expect(capturedBody.input).toEqual([
      { role: 'user', content: [{ type: 'input_text', text: 'hello' }] },
    ]);
    expect(capturedBody.prompt_cache_key).toEqual('session-1');
    expect(capturedBody.prompt_cache_retention).toEqual(undefined);
  } finally {
    globalThis.fetch = originalFetch;
  }
});

tap.test('normalizes OpenCode and Codex auth file formats', async () => {
  const tokenData = createTokenData('workspace-2');
  const opencodeAuth = normalizeOpenAiChatGptAuth({
    openai: {
      type: 'oauth',
      access: tokenData.accessToken,
      refresh: tokenData.refreshToken,
      expires: Date.parse(tokenData.tokenInfo.expiresAt!),
      accountId: 'workspace-2',
    },
  }, 'opencode');
  const codexAuth = normalizeOpenAiChatGptAuth({
    tokens: {
      access_token: tokenData.accessToken,
      refresh_token: tokenData.refreshToken,
      account_id: 'workspace-2',
    },
  }, 'codex');

  expect(opencodeAuth?.accountId).toEqual('workspace-2');
  expect(opencodeAuth?.tokenInfo.chatgptAccountId).toEqual('workspace-2');
  expect(codexAuth?.accountId).toEqual('workspace-2');
  expect(codexAuth?.tokenInfo.chatgptAccountId).toEqual('workspace-2');
});

tap.test('inspects and resolves OpenAI ChatGPT auth sources without exposing tokens', async () => {
  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'smartai-auth-sources-'));
  try {
    const tokenData = createTokenData('workspace-3');
    const opencodePath = path.join(tempDir, 'opencode-auth.json');
    await fs.writeFile(opencodePath, JSON.stringify({
      openai: {
        type: 'oauth',
        access: tokenData.accessToken,
        refresh: tokenData.refreshToken,
        expires: Date.parse(tokenData.tokenInfo.expiresAt!),
        accountId: 'workspace-3',
      },
      anthropic: { type: 'oauth', access: 'keep-me' },
    }));

    const inspections = await inspectOpenAiChatGptAuthSources({
      sources: [{ source: 'opencode', filePath: opencodePath }],
    });
    const resolved = await resolveOpenAiChatGptAuth({
      sources: [{ source: 'opencode', filePath: opencodePath }],
    });

    expect(inspections).toHaveLength(1);
    expect(inspections[0]!.usable).toEqual(true);
    expect(JSON.stringify(inspections)).not.toInclude(tokenData.accessToken);
    expect(resolved?.source).toEqual('opencode');
    expect(resolved?.tokenData.accountId).toEqual('workspace-3');
  } finally {
    await fs.rm(tempDir, { recursive: true, force: true });
  }
});

tap.test('writes OpenCode auth while preserving unrelated providers', async () => {
  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'smartai-opencode-write-'));
  try {
    const tokenData = createTokenData('workspace-4');
    const opencodePath = path.join(tempDir, 'auth.json');
    await fs.writeFile(opencodePath, JSON.stringify({ anthropic: { type: 'oauth', access: 'keep-me' } }));

    await writeOpenAiChatGptAuthFile(opencodePath, tokenData, 'opencode');
    const written = JSON.parse(await fs.readFile(opencodePath, 'utf8')) as any;

    expect(written.anthropic.access).toEqual('keep-me');
    expect(written.openai.type).toEqual('oauth');
    expect(written.openai.accountId).toEqual('workspace-4');
    expect(written.openai.access).toEqual(tokenData.accessToken);
  } finally {
    await fs.rm(tempDir, { recursive: true, force: true });
  }
});

export default tap.start();
