// @vitest-environment node
import { mkdtemp, mkdir, readFile, rm, utimes, writeFile } from 'fs/promises';
import os from 'os';
import path from 'path';

import { afterEach, describe, expect, it } from 'vitest';

import {
  detectCodexLocalAccountArtifacts,
  detectCodexLocalAccountState,
  ensureCodexLegacyAuthFromActiveAccount,
  resolveCodexActiveChatgptAuthFile,
} from '../../../../../src/features/codex-account/main/infrastructure/detectCodexLocalAccountArtifacts';

const tempDirs: string[] = [];

async function makeTempDir(): Promise<string> {
  const dir = await mkdtemp(path.join(os.tmpdir(), 'codex-artifacts-'));
  tempDirs.push(dir);
  return dir;
}

async function makeCodexHome(): Promise<{ codexHome: string; accountsDir: string }> {
  const codexHome = await makeTempDir();
  const accountsDir = path.join(codexHome, 'accounts');
  await mkdir(accountsDir, { recursive: true });
  return { codexHome, accountsDir };
}

afterEach(async () => {
  await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});

function encodeAccountKeyForAuthFilename(accountKey: string): string {
  return Buffer.from(accountKey, 'utf8')
    .toString('base64')
    .replaceAll('+', '-')
    .replaceAll('/', '_')
    .replace(/=+$/u, '');
}

describe('detectCodexLocalAccountArtifacts', () => {
  it('returns true when the Codex accounts registry exists', async () => {
    const accountsDir = await makeTempDir();
    await writeFile(path.join(accountsDir, 'registry.json'), '{}', 'utf8');

    await expect(detectCodexLocalAccountArtifacts(accountsDir)).resolves.toBe(true);
  });

  it('returns true when auth artifacts exist without a registry file', async () => {
    const accountsDir = await makeTempDir();
    await writeFile(path.join(accountsDir, 'chatgpt.auth.json'), '{}', 'utf8');

    await expect(detectCodexLocalAccountArtifacts(accountsDir)).resolves.toBe(true);
  });

  it('returns false when the accounts directory is missing or empty', async () => {
    const missingDir = path.join(await makeTempDir(), 'missing');
    const emptyDir = await makeTempDir();
    await mkdir(emptyDir, { recursive: true });

    await expect(detectCodexLocalAccountArtifacts(missingDir)).resolves.toBe(false);
    await expect(detectCodexLocalAccountArtifacts(emptyDir)).resolves.toBe(false);
  });

  it('detects a locally selected ChatGPT account from the registry and active auth file', async () => {
    const { accountsDir } = await makeCodexHome();
    const activeAccountKey = 'user-test::chatgpt-account';
    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ active_account_key: activeAccountKey }),
      'utf8'
    );
    await writeFile(
      path.join(accountsDir, `${encodeAccountKeyForAuthFilename(activeAccountKey)}.auth.json`),
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { refresh_token: 'refresh-token' } }),
      'utf8'
    );

    await expect(detectCodexLocalAccountState(accountsDir)).resolves.toEqual({
      hasArtifacts: true,
      hasActiveChatgptAccount: true,
    });
  });

  it('resolves the active accounts-format auth file before legacy auth when a registry exists', async () => {
    const { codexHome, accountsDir } = await makeCodexHome();
    const activeAccountKey = 'user-active::chatgpt-account';
    await writeFile(
      path.join(codexHome, 'auth.json'),
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { refresh_token: 'legacy-refresh-token' } }),
      'utf8'
    );
    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ active_account_key: activeAccountKey }),
      'utf8'
    );
    const activeAuthPath = path.join(
      accountsDir,
      `${encodeAccountKeyForAuthFilename(activeAccountKey)}.auth.json`
    );
    await writeFile(
      activeAuthPath,
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { refresh_token: 'active-refresh-token' } }),
      'utf8'
    );

    await expect(resolveCodexActiveChatgptAuthFile(accountsDir)).resolves.toMatchObject({
      authFilePath: activeAuthPath,
      source: 'accounts',
      activeAccountKey,
    });
  });

  it('materializes active accounts-format auth into legacy auth.json for Codex CLI compatibility', async () => {
    const { codexHome, accountsDir } = await makeCodexHome();
    const activeAccountKey = 'user-active::chatgpt-account';
    const authPayload = {
      auth_mode: 'chatgpt',
      tokens: { refresh_token: 'active-refresh-token', access_token: 'active-access-token' },
    };
    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ active_account_key: activeAccountKey }),
      'utf8'
    );
    await writeFile(
      path.join(accountsDir, `${encodeAccountKeyForAuthFilename(activeAccountKey)}.auth.json`),
      JSON.stringify(authPayload),
      'utf8'
    );

    const result = await ensureCodexLegacyAuthFromActiveAccount(accountsDir);

    expect(result).toMatchObject({
      codexHome,
      authFilePath: path.join(codexHome, 'auth.json'),
      source: 'accounts',
      materializedLegacyAuth: true,
    });
    await expect(readFile(path.join(codexHome, 'auth.json'), 'utf8')).resolves.toBe(
      JSON.stringify(authPayload)
    );
  });

  it('does not overwrite a newer synced legacy auth file for the same active account', async () => {
    const { codexHome, accountsDir } = await makeCodexHome();
    const activeAccountKey = 'user-active::chatgpt-account';
    const activeAuthPath = path.join(
      accountsDir,
      `${encodeAccountKeyForAuthFilename(activeAccountKey)}.auth.json`
    );
    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ active_account_key: activeAccountKey }),
      'utf8'
    );
    await writeFile(
      activeAuthPath,
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { refresh_token: 'first-refresh-token' } }),
      'utf8'
    );
    await ensureCodexLegacyAuthFromActiveAccount(accountsDir);

    const refreshedLegacyPayload = JSON.stringify({
      auth_mode: 'chatgpt',
      tokens: { refresh_token: 'runtime-refreshed-token' },
    });
    const legacyAuthPath = path.join(codexHome, 'auth.json');
    await writeFile(legacyAuthPath, refreshedLegacyPayload, 'utf8');
    const future = new Date(Date.now() + 60_000);
    await utimes(legacyAuthPath, future, future);

    const result = await ensureCodexLegacyAuthFromActiveAccount(accountsDir);

    expect(result?.materializedLegacyAuth).toBe(false);
    await expect(readFile(legacyAuthPath, 'utf8')).resolves.toBe(refreshedLegacyPayload);
  });

  it('refreshes legacy auth when the selected accounts-format account changes', async () => {
    const { codexHome, accountsDir } = await makeCodexHome();
    const firstAccountKey = 'user-first::chatgpt-account';
    const secondAccountKey = 'user-second::chatgpt-account';
    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ active_account_key: firstAccountKey }),
      'utf8'
    );
    await writeFile(
      path.join(accountsDir, `${encodeAccountKeyForAuthFilename(firstAccountKey)}.auth.json`),
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { refresh_token: 'first-refresh-token' } }),
      'utf8'
    );
    await writeFile(
      path.join(accountsDir, `${encodeAccountKeyForAuthFilename(secondAccountKey)}.auth.json`),
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { refresh_token: 'second-refresh-token' } }),
      'utf8'
    );
    await ensureCodexLegacyAuthFromActiveAccount(accountsDir);

    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ active_account_key: secondAccountKey }),
      'utf8'
    );

    const result = await ensureCodexLegacyAuthFromActiveAccount(accountsDir);

    expect(result?.materializedLegacyAuth).toBe(true);
    await expect(readFile(path.join(codexHome, 'auth.json'), 'utf8')).resolves.toContain(
      'second-refresh-token'
    );
  });

  it('requires a ChatGPT refresh token for the selected account', async () => {
    const { accountsDir } = await makeCodexHome();
    const activeAccountKey = 'user-test::chatgpt-account';
    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ activeAccountId: activeAccountKey }),
      'utf8'
    );
    await writeFile(
      path.join(accountsDir, `${encodeAccountKeyForAuthFilename(activeAccountKey)}.auth.json`),
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { access_token: 'access-token' } }),
      'utf8'
    );

    await expect(detectCodexLocalAccountState(accountsDir)).resolves.toEqual({
      hasArtifacts: true,
      hasActiveChatgptAccount: false,
    });
  });

  it('falls back to legacy auth.json when the accounts registry is absent', async () => {
    const { codexHome, accountsDir } = await makeCodexHome();
    await writeFile(
      path.join(codexHome, 'auth.json'),
      JSON.stringify({ auth_mode: 'chatgpt', tokens: { refresh_token: 'legacy-refresh-token' } }),
      'utf8'
    );

    await expect(detectCodexLocalAccountState(accountsDir)).resolves.toEqual({
      hasArtifacts: true,
      hasActiveChatgptAccount: true,
    });
  });

  it('keeps artifact detection true but selected-account detection false when the active auth file is missing', async () => {
    const { accountsDir } = await makeCodexHome();
    await writeFile(
      path.join(accountsDir, 'registry.json'),
      JSON.stringify({ active_account_key: 'user-test::missing-auth' }),
      'utf8'
    );

    await expect(detectCodexLocalAccountState(accountsDir)).resolves.toEqual({
      hasArtifacts: true,
      hasActiveChatgptAccount: false,
    });
  });
});
