import { io } from 'socket.io-client';
import { resolveRelayConfig, DEFAULT_RELAY_URL } from './relay-config.js';
import { loadPairingIdentity } from './pairing-identity.js';
import { t } from './i18n.js';
import { fetchRemoteVersionPolicy } from './version-check.js';
function loadRelayUrl() {
    return resolveRelayConfig().relayUrl || DEFAULT_RELAY_URL;
}
function socketProbe(relayUrl, auth, transports, expectIndex) {
    return new Promise((resolve) => {
        const socket = io(relayUrl, {
            transports,
            auth,
            reconnection: false,
            timeout: 12_000,
        });
        let indexRepos = null;
        const transportLabel = transports.join('+');
        const done = (ok, detail) => {
            socket.disconnect();
            resolve({ ok, detail });
        };
        socket.on('connect', () => {
            if (!expectIndex) {
                done(true, `connect id=${socket.id} transport=${socket.io.engine.transport.name}`);
                return;
            }
            socket.emit('agents:refresh');
        });
        socket.on('agents:index', (idx) => {
            indexRepos = idx?.repos?.length ?? 0;
            done(true, `connect transport=${socket.io.engine.transport.name} agents:index repos=${indexRepos}`);
        });
        socket.on('connect_error', (err) => {
            done(false, `${transportLabel}: ${err.message}`);
        });
        setTimeout(() => {
            if (socket.connected && !expectIndex)
                return;
            if (socket.connected && expectIndex) {
                done(false, `${transportLabel}: connect ok, agents:index timeout (wrong room?)`);
                return;
            }
            done(false, `${transportLabel}: timeout`);
        }, expectIndex ? 10_000 : 8_000);
    });
}
function historyProbe(relayUrl, auth, agentId, title) {
    return new Promise((resolve) => {
        const socket = io(relayUrl, {
            transports: ['websocket', 'polling'],
            auth,
            reconnection: false,
            timeout: 15_000,
        });
        const t0 = Date.now();
        const done = (ok, detail) => {
            socket.disconnect();
            resolve({ ok, detail });
        };
        socket.on('connect', () => {
            const requestId = `diag-${Date.now().toString(36)}`;
            socket.emit('agents:history', { agentId, title, requestId, limit: 15 });
        });
        socket.on('agents:history', (h) => {
            done(true, `${h.messages?.length ?? 0} msgs for ${h.agentId ?? agentId} in ${Date.now() - t0}ms`);
        });
        socket.on('agents:history:error', (e) => {
            done(false, `agents:history:error ${e.code ?? 'unknown'} ${e.message ?? ''}`.trim());
        });
        socket.on('connect_error', (err) => done(false, err.message));
        setTimeout(() => done(false, 'agents:history timeout (20s)'), 20_000);
    });
}
async function fetchAnyAgentFromIndex(params) {
    const base = params.relayUrl.replace(/\/$/, '');
    try {
        const res = await fetch(`${base}/api/agents/index`, {
            headers: {
                Authorization: `Bearer ${params.token}`,
                'X-CursorConnect-Room': params.roomId,
            },
            signal: AbortSignal.timeout(12_000),
        });
        if (!res.ok)
            return null;
        const idx = (await res.json());
        for (const repo of idx.repos ?? []) {
            for (const a of repo.agents ?? []) {
                const id = String(a?.id ?? '').trim();
                if (id && !id.startsWith('sidebar-')) {
                    const title = String(a?.title ?? '').trim();
                    return { agentId: id, ...(title ? { title } : {}) };
                }
            }
        }
        return null;
    }
    catch {
        return null;
    }
}
export async function runDiagnose() {
    const identity = loadPairingIdentity();
    const relayUrl = (loadRelayUrl() || 'https://cc.fanpay.online').replace(/\/$/, '');
    const checks = [];
    const policy = await fetchRemoteVersionPolicy(relayUrl);
    if (!identity) {
        return {
            at: new Date().toISOString(),
            relayUrl,
            roomId: '',
            checks: [
                {
                    id: 'identity',
                    ok: false,
                    detail: t('diagnose.noIdentity'),
                    hint: t('diagnose.hintStart'),
                },
            ],
        };
    }
    const roomId = identity.roomId;
    const token = identity.clientToken;
    // 1 Relay HTTP
    try {
        const res = await fetch(`${relayUrl}/health`);
        const j = (await res.json());
        checks.push({
            id: 'relay.health',
            ok: Boolean(j.ok),
            detail: `ok=${j.ok} connector=${j.connector}`,
            hint: j.connector ? undefined : t('diagnose.hintStartMac'),
        });
    }
    catch (e) {
        checks.push({
            id: 'relay.health',
            ok: false,
            detail: e.message,
            hint: t('diagnose.hintRelayUrl'),
        });
    }
    // 2 Room diagnostics API
    try {
        const q = new URLSearchParams({ roomId, token });
        const res = await fetch(`${relayUrl}/api/diagnostics?${q}`);
        const j = (await res.json());
        const ok = Boolean(j.ok);
        checks.push({
            id: 'relay.room',
            ok: ok && Boolean(j.connectorInRoom) && Boolean(j.tokenRegistered),
            detail: JSON.stringify(j),
            hint: !j.connectorInRoom
                ? t('diagnose.notInRoom')
                : !j.tokenRegistered
                    ? t('diagnose.tokenNotRegistered')
                    : !j.tokenAccepted
                        ? t('diagnose.tokenNotWhitelisted')
                        : undefined,
        });
    }
    catch (e) {
        checks.push({
            id: 'relay.room',
            ok: false,
            detail: e.message,
            hint: t('diagnose.hintDeployRelay'),
        });
    }
    // 3 Local bridge
    try {
        const res = await fetch('http://127.0.0.1:3847/health');
        const j = (await res.json());
        checks.push({
            id: 'cursor-connect.local',
            ok: Boolean(j.ok),
            detail: `ok=${j.ok} cdp=${j.cdp}`,
            hint: j.cdp ? undefined : t('diagnose.hintCdp9222'),
        });
    }
    catch (e) {
        checks.push({
            id: 'cursor-connect.local',
            ok: false,
            detail: e.message,
            hint: t('diagnose.hintStart'),
        });
    }
    // 4 Pairing code (не вызываем POST /api/pair — код одноразовый)
    const code = identity.pairingCode?.replace(/[^A-Z0-9]/gi, '').toUpperCase() ?? '';
    const codeValid = code.length === 6 && identity.pairingCodeExpiresAt > Date.now();
    if (codeValid) {
        const secLeft = Math.max(0, Math.floor((identity.pairingCodeExpiresAt - Date.now()) / 1000));
        checks.push({
            id: 'pair.code',
            ok: true,
            detail: `active ${code} (~${secLeft}s)`,
            hint: t('diagnose.hintEnterInApp'),
        });
    }
    else {
        checks.push({
            id: 'pair.code',
            ok: false,
            detail: t('diagnose.codeExpired'),
            hint: t('diagnose.hintStart'),
        });
    }
    // Relay requires clientVersion for client sockets.
    // Diagnose is a transport probe (not a real app session), so if policy fetch fails,
    // use a high version to avoid false negatives.
    const auth = { token, roomId, clientVersion: policy?.minAppVersion ?? '999.0.0' };
    const ws = await socketProbe(relayUrl, auth, ['websocket'], true);
    checks.push({
        id: 'socket.ws+room',
        ok: ws.ok,
        detail: ws.detail,
        hint: ws.ok ? undefined : t('diagnose.hintAppTransports'),
    });
    const poll = await socketProbe(relayUrl, auth, ['polling'], true);
    checks.push({
        id: 'socket.poll+room',
        ok: poll.ok,
        detail: poll.detail,
        hint: poll.ok ? undefined : t('diagnose.hintPollingBlocked'),
    });
    const wrongRoom = await socketProbe(relayUrl, 
    // Provide clientVersion too; we want to test room mismatch, not version gate.
    { token, roomId: 'default', clientVersion: policy?.minAppVersion ?? '999.0.0' }, ['websocket', 'polling'], true);
    checks.push({
        id: 'socket.wrong-room',
        ok: !wrongRoom.ok,
        detail: wrongRoom.detail,
        hint: wrongRoom.ok
            ? t('diagnose.wrongRoom')
            : undefined,
    });
    const probeAgent = await fetchAnyAgentFromIndex({ relayUrl, roomId, token });
    const history = probeAgent
        ? await historyProbe(relayUrl, auth, probeAgent.agentId, probeAgent.title)
        : { ok: false, detail: 'agents:index unavailable — cannot probe agents:history' };
    checks.push({
        id: 'socket.history',
        ok: history.ok,
        detail: history.detail,
        hint: history.ok ? undefined : t('diagnose.hintHistory'),
    });
    return { at: new Date().toISOString(), relayUrl, roomId, checks };
}
export function formatDiagnoseReport(report) {
    const lines = [
        t('diagnose.header', { at: report.at }),
        t('diagnose.relay', { url: report.relayUrl }),
        t('diagnose.room', { id: report.roomId }),
        '',
    ];
    for (const c of report.checks) {
        lines.push(`${c.ok ? t('diagnose.pass') : t('diagnose.fail')}  ${c.id}`);
        lines.push(`      ${c.detail}`);
        if (c.hint)
            lines.push(`${t('diagnose.hintPrefix')}${c.hint}`);
    }
    const failed = report.checks.filter((c) => !c.ok).length;
    lines.push('', failed === 0
        ? t('diagnose.allPass')
        : t('diagnose.problems', { count: failed }));
    return lines.join('\n');
}
