import { mkdtempSync, statSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { extractAccountId, parseJwtClaims } from './openai.js';

function base64UrlEncode(obj: unknown): string {
  return Buffer.from(JSON.stringify(obj)).toString('base64url');
}

function makeJwt(payload: Record<string, unknown>): string {
  const header = base64UrlEncode({ alg: 'none', typ: 'JWT' });
  const body = base64UrlEncode(payload);
  // Signature is ignored by parseJwtClaims.
  return `${header}.${body}.sig`;
}

describe('OpenAI OAuth helpers', () => {
  it('parseJwtClaims returns undefined for non-jwt strings', () => {
    expect(parseJwtClaims('not-a-jwt')).toBeUndefined();
  });

  it('parseJwtClaims parses base64url payload', () => {
    const token = makeJwt({ chatgpt_account_id: 'acct_123' });
    const claims = parseJwtClaims(token);
    expect(claims?.chatgpt_account_id).toBe('acct_123');
  });

  it('extractAccountId prefers chatgpt_account_id', () => {
    const tokens = {
      access_token: makeJwt({ chatgpt_account_id: 'acct_a' }),
      refresh_token: 'rt',
      id_token: makeJwt({ chatgpt_account_id: 'acct_b' }),
    };
    expect(extractAccountId(tokens)).toBe('acct_b');
  });

  it('extractAccountId falls back to organizations[0].id', () => {
    const tokens = {
      access_token: makeJwt({ organizations: [{ id: 'org_1' }] }),
      refresh_token: 'rt',
    };
    expect(extractAccountId(tokens)).toBe('org_1');
  });
});

describe('auth/openai api key storage', () => {
  const originalHome = process.env.HOME;
  const originalEnvKey = process.env.OPENAI_API_KEY;

  let homeDir: string;

  beforeEach(() => {
    homeDir = mkdtempSync(join(tmpdir(), 'flynn-auth-openai-'));
    process.env.HOME = homeDir;
    delete process.env.OPENAI_API_KEY;
    vi.resetModules();
  });

  afterEach(() => {
    process.env.HOME = originalHome;
    if (originalEnvKey) {
      process.env.OPENAI_API_KEY = originalEnvKey;
    } else {
      delete process.env.OPENAI_API_KEY;
    }
  });

  it('stores, loads, and clears OpenAI API key (preserves OAuth entry)', async () => {
    const mod = await import('./openai.js');

    expect(mod.loadStoredOpenAIApiKey()).toBeNull();
    expect(mod.loadStoredOpenAIAuth()).toBeNull();

    mod.storeOpenAIApiKey('sk-test');
    expect(mod.loadStoredOpenAIApiKey()).toBe('sk-test');

    const authFile = join(homeDir, '.config/flynn/auth.json');
    const mode = statSync(authFile).mode & 0o777;
    expect(mode).toBe(0o600);

    const oauth = {
      access_token: 'at',
      refresh_token: 'rt',
      expires_at: Date.now() + 60_000,
      created_at: new Date().toISOString(),
    };
    mod.storeOpenAIAuth(oauth);

    expect(mod.loadStoredOpenAIAuth()?.access_token).toBe('at');
    expect(mod.loadStoredOpenAIApiKey()).toBe('sk-test');

    mod.clearOpenAIAuth();
    expect(mod.loadStoredOpenAIAuth()).toBeNull();
    expect(mod.loadStoredOpenAIApiKey()).toBe('sk-test');

    mod.clearOpenAIApiKey();
    expect(mod.loadStoredOpenAIApiKey()).toBeNull();
  });

  it('getOpenAIApiKey prefers environment variable', async () => {
    process.env.OPENAI_API_KEY = 'sk-env';
    const mod = await import('./openai.js');
    expect(mod.getOpenAIApiKey()).toBe('sk-env');
  });
});
