/**
 * E2E: Jira (Composio) connector flow.
 *
 * Includes an extra test verifying the subdomain required-field validation:
 * the Connect button must be disabled (or show an inline error) when no
 * valid Atlassian subdomain is entered.
 */
import { waitForApp } from '../helpers/app-helpers';
import {
  assertConnectorCardVisible,
  assertModalPhase,
  assertSessionNotNuked,
  injectComposioFault,
  openConnectorModal,
  seedComposioConnection,
  seedComposioToolkits,
} from '../helpers/composio-helpers';
import { callOpenhumanRpc } from '../helpers/core-rpc';
import { triggerAuthDeepLinkBypass } from '../helpers/deep-link-helpers';
import {
  textExists,
  waitForText,
  waitForWebView,
  waitForWindowVisible,
} from '../helpers/element-helpers';
import { completeOnboardingIfVisible, navigateToSkills } from '../helpers/shared-flows';
import {
  clearRequestLog,
  getRequestLog,
  resetMockBehavior,
  setMockBehavior,
  startMockServer,
  stopMockServer,
} from '../mock-server';

const LOG = '[ConnectorJiraE2E]';
const CONNECTOR_NAME = 'Jira';
const TOOLKIT_SLUG = 'jira';
const AUTH_TOKEN = 'e2e-connector-jira-token';

describe('Jira Composio connector flow', () => {
  before(async function () {
    this.timeout(90_000);
    await startMockServer();
    seedComposioToolkits([TOOLKIT_SLUG]);
    seedComposioConnection(TOOLKIT_SLUG, 'ACTIVE', 'c-jira-1');
    await waitForApp();
    clearRequestLog();
    await triggerAuthDeepLinkBypass(AUTH_TOKEN);
    await waitForWindowVisible(25_000);
    await waitForWebView(15_000);
    await completeOnboardingIfVisible(LOG);
  });

  after(async () => {
    await stopMockServer();
  });

  afterEach(async () => {
    resetMockBehavior();
    seedComposioToolkits([TOOLKIT_SLUG]);
    seedComposioConnection(TOOLKIT_SLUG, 'ACTIVE', 'c-jira-1');
  });

  it('card is visible and selectable', async function () {
    this.timeout(60_000);
    await assertConnectorCardVisible(CONNECTOR_NAME);
    console.log(`${LOG} PASS: card visible`);
  });

  it('connect modal renders subdomain input field for Jira', async function () {
    this.timeout(60_000);
    // Seed as idle (no active connection) so we see the connect flow
    seedComposioConnection(TOOLKIT_SLUG, 'CONNECTING', 'c-jira-idle');
    setMockBehavior('composioConnections', JSON.stringify([]));
    await navigateToSkills();
    await waitForText(CONNECTOR_NAME, 10_000);
    const modal = await openConnectorModal(CONNECTOR_NAME);
    expect(modal).toBeTruthy();
    // The Jira connect modal should render a subdomain input per toolkitRequiredFields.ts
    // It uses data-testid="composio-required-subdomain"
    // @ts-expect-error -- browser global is injected by WDIO at runtime, not typed in this env
    const hasSubdomainInput = await browser
      .execute(() => {
        return (
          document.querySelector('[data-testid="composio-required-subdomain"]') !== null ||
          document.querySelector('input[placeholder*="subdomain"]') !== null ||
          // fallback: any .atlassian.net suffix label
          Array.from(document.querySelectorAll('*')).some(el =>
            (el.textContent ?? '').includes('.atlassian.net')
          )
        );
      })
      .catch(() => false);
    expect(hasSubdomainInput).toBe(true);
    console.log(`${LOG} PASS: subdomain input field visible in Jira modal`);
    // Close modal by pressing Escape
    // @ts-expect-error -- browser global is injected by WDIO at runtime, not typed in this env
    await browser.keys(['Escape']).catch(() => {});
    await assertSessionNotNuked();
  });

  it('auth/connect flow with subdomain extra_params routes correctly', async function () {
    this.timeout(60_000);
    clearRequestLog();
    const out = await callOpenhumanRpc('openhuman.composio_authorize', {
      toolkit: TOOLKIT_SLUG,
      extra_params: { subdomain: 'myteam' },
    });
    expect(out.ok).toBe(true);
    const log = getRequestLog();
    const authReq = log.find(r => r.method === 'POST' && r.url.includes('/composio/authorize'));
    expect(authReq).toBeDefined();
    const body = JSON.parse(authReq?.body || '{}');
    expect(body.toolkit).toBe(TOOLKIT_SLUG);
    console.log(`${LOG} PASS: authorize with subdomain extra_params routed correctly`);
  });

  it('connected state persists after reconnect/reload', async function () {
    this.timeout(60_000);
    seedComposioConnection(TOOLKIT_SLUG, 'ACTIVE', 'c-jira-1');
    const out = await callOpenhumanRpc('openhuman.composio_list_connections', {});
    expect(out.ok).toBe(true);
    const result = (out.result as { result?: unknown })?.result ?? out.result;
    const connections = (result as { connections?: unknown[] })?.connections ?? [];
    const hit = (connections as { toolkit?: string; status?: string }[]).find(
      c => c.toolkit?.toLowerCase() === TOOLKIT_SLUG
    );
    expect(hit).toBeDefined();
    expect(hit?.status).toBe('ACTIVE');
    console.log(`${LOG} PASS: connected state persists`);
  });

  it('composio_sync RPC routes to mock backend', async function () {
    this.timeout(30_000);
    clearRequestLog();
    await callOpenhumanRpc('openhuman.composio_sync', { toolkit: TOOLKIT_SLUG });
    const syncLog = getRequestLog();
    const syncReq = syncLog.find(r => r.method === 'POST' && r.url.includes('/composio/sync'));
    expect(syncReq).toBeDefined();
    await assertSessionNotNuked();
    console.log(`${LOG} PASS: sync does not nuke session`);
  });

  it('composio_execute routes a basic task', async function () {
    this.timeout(30_000);
    clearRequestLog();
    await callOpenhumanRpc('openhuman.composio_execute', {
      connection_id: 'c-jira-1',
      action: 'JIRA_LIST_ISSUES',
      params: {},
    });
    const log = getRequestLog();
    const execReq = log.find(r => r.url.includes('/composio/execute'));
    expect(execReq).toBeDefined();
    expect(execReq!.method).toBe('POST');
    console.log(`${LOG} PASS: execute routed`);
  });

  it('failed connection shows error state, not blank screen', async function () {
    this.timeout(60_000);
    seedComposioConnection(TOOLKIT_SLUG, 'FAILED', 'c-jira-fail');
    await navigateToSkills();
    await waitForText(CONNECTOR_NAME, 10_000);
    expect(await textExists(CONNECTOR_NAME)).toBe(true);
    await assertSessionNotNuked();
    console.log(`${LOG} PASS: failed state does not blank screen`);
  });

  it('expired auth shows Reconnect button and does not log user out', async function () {
    this.timeout(60_000);
    seedComposioConnection(TOOLKIT_SLUG, 'EXPIRED', 'c-jira-expired');
    await navigateToSkills();
    await waitForText(CONNECTOR_NAME, 10_000);
    const modal = await openConnectorModal(CONNECTOR_NAME);
    if (modal) await assertModalPhase('expired', CONNECTOR_NAME);
    await assertSessionNotNuked();
    console.log(`${LOG} PASS: expired auth does not log user out`);
  });

  it('unrelated 401 on composio route does not nuke session', async function () {
    this.timeout(60_000);
    injectComposioFault(400);
    await callOpenhumanRpc('openhuman.composio_execute', {
      connection_id: 'c-jira-1',
      action: 'JIRA_LIST_ISSUES',
      params: {},
    });
    await assertSessionNotNuked();
    console.log(`${LOG} PASS: 401-class error does not nuke session`);
  });

  it('disconnect flow removes connection', async function () {
    this.timeout(60_000);
    seedComposioConnection(TOOLKIT_SLUG, 'ACTIVE', 'c-jira-1');
    clearRequestLog();
    await callOpenhumanRpc('openhuman.composio_delete_connection', { connection_id: 'c-jira-1' });
    const log = getRequestLog();
    const deleteReq = log.find(
      r => r.method === 'DELETE' && r.url.includes('/composio/connections/')
    );
    expect(deleteReq).toBeDefined();
    console.log(`${LOG} PASS: disconnect routed DELETE`);
    await assertSessionNotNuked();
  });
});
