import { expect, test } from '@playwright/test';
import type { Page } from '@playwright/test';

const STORAGE_KEY = 'open-design:config';
const LOCALE_KEY = 'open-design:locale';
const OPEN_SETTINGS_LABEL = /Open settings|打开设置|開啟設定/i;
const SETTINGS_MENU_LABEL = /^Settings$|^设置$|^設定$/i;
const LOCAL_CLI_LABEL = /Local CLI|本机 CLI|本地 CLI/i;

test.describe.configure({ timeout: 30_000 });

type AppConfigSeed = Record<string, unknown>;

type AgentFixture = {
  id: string;
  name: string;
  bin: string;
  available: boolean;
  version?: string | null;
  models?: Array<{ id: string; label: string }>;
};

type ConnectionTestFixture = {
  ok: boolean;
  kind: string;
  latencyMs: number;
  agentName?: string;
  sample?: string;
  detail?: string;
  configuredExecutablePath?: string;
  detectedExecutablePath?: string;
  usedExecutablePath?: string;
  usedExecutableSource?: 'configured' | 'path' | 'fallback_invalid' | 'fallback_failed';
};

const CODEX_AGENT: AgentFixture = {
  id: 'codex',
  name: 'Codex CLI',
  bin: 'codex',
  available: true,
  version: '0.130.0',
  models: [{ id: 'default', label: 'Default' }],
};

function baseConfig(overrides: Partial<AppConfigSeed> = {}): AppConfigSeed {
  return {
    mode: 'daemon',
    apiKey: '',
    apiProtocol: 'openai',
    apiVersion: '',
    baseUrl: 'https://api.openai.com/v1',
    model: 'gpt-4o',
    apiProviderBaseUrl: 'https://api.openai.com/v1',
    agentId: 'codex',
    skillId: null,
    designSystemId: null,
    onboardingCompleted: true,
    mediaProviders: {},
    agentModels: {},
    agentCliEnv: {},
    ...overrides,
  };
}

async function readSavedConfig(page: Page) {
  return page.evaluate((key) => {
    const raw = window.localStorage.getItem(key);
    return raw ? JSON.parse(raw) : null;
  }, STORAGE_KEY);
}

async function waitForLoadingToClear(page: Page) {
  await expect(page.getByText('Loading Open Design…')).toHaveCount(0, { timeout: 15_000 });
}

async function gotoEntryHome(page: Page) {
  await page.goto('/', { waitUntil: 'domcontentloaded' });
  await waitForLoadingToClear(page);
  const privacyDialog = page.getByRole('dialog').filter({ hasText: 'Help us improve Open Design' });
  if (await privacyDialog.isVisible()) {
    await privacyDialog.getByRole('button', { name: /not now/i }).click();
  }
  await expect(page.getByRole('button', { name: OPEN_SETTINGS_LABEL })).toBeVisible();
}

async function openLocalCliSettings(
  page: Page,
  {
    config,
    locale = 'en',
    agents = [CODEX_AGENT],
    onConnectionTest,
  }: {
    config: AppConfigSeed;
    locale?: string;
    agents?: AgentFixture[];
    onConnectionTest: (payload: Record<string, unknown>) => ConnectionTestFixture;
  },
) {
  await page.addInitScript(
    ({ key, value, localeKey, localeValue }) => {
      window.localStorage.setItem(key, JSON.stringify(value));
      window.localStorage.setItem(localeKey, localeValue);
    },
    { key: STORAGE_KEY, value: config, localeKey: LOCALE_KEY, localeValue: locale },
  );

  await page.route('**/api/health', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: '{"ok":true}',
    });
  });

  await page.route('**/api/app-config', async (route) => {
    if (route.request().method() === 'GET') {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          config: {
            onboardingCompleted: true,
            agentId: typeof config.agentId === 'string' ? config.agentId : 'codex',
            agentCliEnv: config.agentCliEnv ?? {},
            agentModels: config.agentModels ?? {},
            skillId: null,
            designSystemId: null,
            disabledSkills: [],
            disabledDesignSystems: [],
          },
        }),
      });
      return;
    }

    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ ok: true }),
    });
  });

  await page.route('**/api/agents', async (route) => {
    await route.fulfill({ json: { agents } });
  });

  await page.route('**/api/test/connection', async (route) => {
    const payload = route.request().postDataJSON() as Record<string, unknown>;
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify(onConnectionTest(payload)),
    });
  });

  await gotoEntryHome(page);
  await page.getByRole('button', { name: OPEN_SETTINGS_LABEL }).click();
  const menu = page.getByRole('menu');
  if (await menu.isVisible().catch(() => false)) {
    await menu.getByRole('button', { name: SETTINGS_MENU_LABEL }).click();
  }

  const dialog = page.getByRole('dialog');
  await expect(dialog).toBeVisible();
  await dialog.getByRole('tab', { name: LOCAL_CLI_LABEL }).click();
  const codexCard = dialog.getByRole('button', { name: /Codex CLI/i });
  await expect(codexCard).toBeVisible();
  await codexCard.click();
  await dialog.getByTestId('settings-cli-env').evaluate((details) => {
    if (details instanceof HTMLDetailsElement) details.open = true;
  });
  await expect(
    dialog.getByLabel(/Codex executable path|Codex 可执行文件路径/i),
  ).toBeVisible();
  return dialog;
}

test.describe('Settings Local CLI Codex fallback UX', () => {
  test('[P0] shows fallback repair actions and can replace the saved path with the detected Codex binary', async ({ page }) => {
    const configuredPath = '/bad/codex';
    const detectedPath = '/usr/local/bin/codex';
    let lastRequest: Record<string, unknown> | null = null;

    const dialog = await openLocalCliSettings(page, {
      config: baseConfig({
        agentCliEnv: { codex: { CODEX_BIN: configuredPath } },
      }),
      onConnectionTest: (payload) => {
        lastRequest = payload;
        return {
          ok: true,
          kind: 'success',
          latencyMs: 18,
          agentName: 'Codex CLI',
          sample: 'ready',
          configuredExecutablePath: configuredPath,
          detectedExecutablePath: detectedPath,
          usedExecutablePath: detectedPath,
          usedExecutableSource: 'fallback_invalid',
        };
      },
    });

    await expect(
      dialog.getByLabel(/Codex executable path|Codex 可执行文件路径/i),
    ).toHaveValue(configuredPath);
    await dialog.getByRole('button', { name: 'Test' }).click();

    const status = dialog.locator('.settings-test-status');
    await expect(dialog.getByText('Use detected Codex')).toBeVisible();
    await expect(dialog.getByText('Clear custom path')).toBeVisible();
    await expect(dialog.getByText('The saved Codex path is not the binary this test should keep using.')).toBeVisible();
    await expect(status).toContainText(configuredPath);
    await expect(status).toContainText(detectedPath);

    expect(lastRequest).toMatchObject({
      mode: 'agent',
      agentId: 'codex',
      agentCliEnv: {
        codex: {
          CODEX_BIN: configuredPath,
        },
      },
    });

    await dialog.getByRole('button', { name: 'Use detected Codex' }).click();
    await expect.poll(async () => readSavedConfig(page)).toMatchObject({
      agentCliEnv: {
        codex: {
          CODEX_BIN: detectedPath,
        },
      },
    });
    await expect(
      dialog.getByLabel(/Codex executable path|Codex 可执行文件路径/i),
    ).toHaveValue(detectedPath);
    await expect(dialog.getByRole('button', { name: 'Use detected Codex' })).toHaveCount(0);
  });

  test('[P0] can clear an unusable custom Codex path after a fallback_failed test result', async ({ page }) => {
    const configuredPath = '/Applications/Codex.app/Contents/Resources/codex';
    const detectedPath = '/opt/homebrew/bin/codex';

    const dialog = await openLocalCliSettings(page, {
      config: baseConfig({
        agentCliEnv: { codex: { CODEX_BIN: configuredPath } },
      }),
      onConnectionTest: () => ({
        ok: true,
        kind: 'success',
        latencyMs: 33,
        agentName: 'Codex CLI',
        sample: 'fallback ok',
        configuredExecutablePath: configuredPath,
        detectedExecutablePath: detectedPath,
        usedExecutablePath: detectedPath,
        usedExecutableSource: 'fallback_failed',
      }),
    });

    await dialog.getByRole('button', { name: 'Test' }).click();
    const status = dialog.locator('.settings-test-status');
    await expect(dialog.getByRole('button', { name: 'Clear custom path' })).toBeVisible();
    await expect(status).toContainText(configuredPath);
    await expect(status).toContainText(detectedPath);

    await dialog.getByRole('button', { name: 'Clear custom path' }).click();

    await expect.poll(async () => readSavedConfig(page)).toMatchObject({
      agentCliEnv: {},
    });
    await expect(
      dialog.getByLabel(/Codex executable path|Codex 可执行文件路径/i),
    ).toHaveValue('');
    await expect(dialog.getByRole('button', { name: 'Clear custom path' })).toHaveCount(0);
  });

});
