/**
 * KeyboardDiagnosticsAgent — A2A pipeline for LysnrAI iOS keyboard extension diagnostics.
 *
 * Agent roster (3 steps):
 *   1. KeyboardErrorScannerAgent — query telemetry for 'error' events from keyboard install IDs
 *   2. DiagnosticsSessionAgent   — open a diagnostics session targeting each erroring installId
 *   3. BugReportDraftAgent       — assemble structured bug report per install ID with error details
 *
 * MCP tools:
 *   lysnrai.keyboard.diagnose(from?, to?, minErrors?) — run pipeline
 */

import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { registerTool } from '../tools/registry.js';
import type { McpToolRequest } from '../tools/types.js';
import {
  telemetryQuery,
  diagnosticsCreateSession,
  diagnosticsUpdateSession,
  type DebugSession,
} from '../../lib/platform-client.js';

// ── Types ──────────────────────────────────────────────────────────────────────

interface KeyboardErrorGroup {
  anonymousInstallId: string;
  errorCount: number;
  errorTypes: string[];
  firstSeen: string;
  lastSeen: string;
  rawEvents: Array<Record<string, unknown>>;
}

interface DiagnosticsResult {
  anonymousInstallId: string;
  session: DebugSession | null;
  skipReason?: string;
}

interface BugReportDraft {
  anonymousInstallId: string;
  diagnosticsSessionId: string | null;
  errorCount: number;
  errorTypes: string[];
  firstSeen: string;
  lastSeen: string;
  reproSteps: string;
  severity: 'critical' | 'high' | 'medium';
}

export interface KeyboardDiagnosticsReport {
  runId: string;
  productId: 'lysnrai';
  from: string;
  to: string;
  installsScanned: number;
  erroringInstalls: number;
  diagnosticsOpened: number;
  bugReports: BugReportDraft[];
  summary: string;
  generatedAt: string;
}

// ── Step 1: KeyboardErrorScannerAgent ─────────────────────────────────────────

async function scanKeyboardErrors(
  fromTime: string,
  toTime: string,
  minErrors: number,
  opts: { token?: string; requestId?: string }
): Promise<KeyboardErrorGroup[]> {
  const installMap = new Map<string, KeyboardErrorGroup>();

  try {
    const result = await telemetryQuery(
      {
        productId: 'lysnrai',
        eventType: 'error',
        from: fromTime,
        to: toTime,
        limit: 500,
      },
      { ...opts, productId: 'lysnrai' }
    );

    const events = result.events as Array<Record<string, unknown>>;

    for (const event of events) {
      const props = (event['properties'] as Record<string, unknown>) ?? {};
      const surface = String(props['surface'] ?? event['surface'] ?? '').toLowerCase();
      if (!surface.includes('keyboard')) continue;

      const installId = String(
        props['anonymousInstallId'] ??
          event['anonymousId'] ??
          event['anonymousInstallId'] ??
          'unknown'
      );
      const errorType = String(
        props['errorType'] ?? props['error_type'] ?? event['eventType'] ?? 'unknown'
      );
      const timestamp = String(
        event['timestamp'] ?? event['createdAt'] ?? new Date().toISOString()
      );

      const group = installMap.get(installId) ?? {
        anonymousInstallId: installId,
        errorCount: 0,
        errorTypes: [],
        firstSeen: timestamp,
        lastSeen: timestamp,
        rawEvents: [],
      };

      group.errorCount++;
      if (!group.errorTypes.includes(errorType)) group.errorTypes.push(errorType);
      if (timestamp < group.firstSeen) group.firstSeen = timestamp;
      if (timestamp > group.lastSeen) group.lastSeen = timestamp;
      group.rawEvents.push(event);

      installMap.set(installId, group);
    }
  } catch {
    // best-effort
  }

  return Array.from(installMap.values()).filter(g => g.errorCount >= minErrors);
}

// ── Step 2: DiagnosticsSessionAgent ───────────────────────────────────────────

async function openDiagnosticsSession(
  group: KeyboardErrorGroup,
  opts: { token?: string; requestId?: string }
): Promise<DiagnosticsResult> {
  try {
    const session = await diagnosticsCreateSession(
      {
        productId: 'lysnrai',
        targetAnonymousId: group.anonymousInstallId,
        collectionLevel: 'debug',
        captureLogs: true,
      },
      opts
    );
    return { anonymousInstallId: group.anonymousInstallId, session };
  } catch (err) {
    return {
      anonymousInstallId: group.anonymousInstallId,
      session: null,
      skipReason: err instanceof Error ? err.message : String(err),
    };
  }
}

// ── Step 3: BugReportDraftAgent ───────────────────────────────────────────────

async function closeDiagnosticsAndDraft(
  group: KeyboardErrorGroup,
  diagnosticsResult: DiagnosticsResult,
  opts: { requestId?: string }
): Promise<BugReportDraft> {
  if (diagnosticsResult.session) {
    try {
      await diagnosticsUpdateSession(diagnosticsResult.session.id, { status: 'completed' }, opts);
    } catch {
      // best-effort
    }
  }

  const severity: 'critical' | 'high' | 'medium' =
    group.errorCount >= 10 ? 'critical' : group.errorCount >= 5 ? 'high' : 'medium';

  const reproSteps = [
    `1. Open LysnrAI iOS keyboard extension on device with installId ${group.anonymousInstallId}.`,
    `2. Trigger dictation — error types observed: ${group.errorTypes.join(', ')}.`,
    `3. First seen ${group.firstSeen}, last seen ${group.lastSeen} (${group.errorCount} occurrences).`,
    diagnosticsResult.session
      ? `4. Diagnostics session ${diagnosticsResult.session.id} opened — check logs for stack traces.`
      : '4. Diagnostics session could not be opened automatically.',
  ].join('\n');

  return {
    anonymousInstallId: group.anonymousInstallId,
    diagnosticsSessionId: diagnosticsResult.session?.id ?? null,
    errorCount: group.errorCount,
    errorTypes: group.errorTypes,
    firstSeen: group.firstSeen,
    lastSeen: group.lastSeen,
    reproSteps,
    severity,
  };
}

// ── Pipeline runner ────────────────────────────────────────────────────────────

async function runKeyboardDiagnosticsPipeline(
  fromTime: string,
  toTime: string,
  minErrors: number,
  req: McpToolRequest
): Promise<KeyboardDiagnosticsReport> {
  const runId = randomUUID();
  const opts = { token: req.headers.authorization?.slice(7), requestId: req.id };

  req.log.info(
    { runId, stepId: 'scan', fromTime, toTime, minErrors },
    'KeyboardErrorScannerAgent start'
  );
  const errorGroups = await scanKeyboardErrors(fromTime, toTime, minErrors, opts);
  req.log.info(
    { runId, stepId: 'scan', erroringInstalls: errorGroups.length },
    'KeyboardErrorScannerAgent done'
  );

  req.log.info(
    { runId, stepId: 'diagnostics', count: errorGroups.length },
    'DiagnosticsSessionAgent start'
  );
  const diagnosticsResults: DiagnosticsResult[] = [];
  for (const group of errorGroups) {
    const result = await openDiagnosticsSession(group, opts);
    diagnosticsResults.push(result);
  }
  const opened = diagnosticsResults.filter(r => r.session !== null).length;
  req.log.info({ runId, stepId: 'diagnostics', opened }, 'DiagnosticsSessionAgent done');

  req.log.info({ runId, stepId: 'draft', count: errorGroups.length }, 'BugReportDraftAgent start');
  const bugReports: BugReportDraft[] = [];
  for (let i = 0; i < errorGroups.length; i++) {
    const draft = await closeDiagnosticsAndDraft(errorGroups[i]!, diagnosticsResults[i]!, {
      requestId: req.id,
    });
    bugReports.push(draft);
  }

  const summary =
    errorGroups.length === 0
      ? `No keyboard errors with >= ${minErrors} occurrence(s) found in the specified window.`
      : `${errorGroups.length} install(s) with keyboard errors detected. ${opened} diagnostics session(s) opened. ${bugReports.filter(r => r.severity === 'critical').length} critical, ${bugReports.filter(r => r.severity === 'high').length} high severity.`;

  req.log.info({ runId, stepId: 'draft', summary }, 'BugReportDraftAgent done');

  return {
    runId,
    productId: 'lysnrai',
    from: fromTime,
    to: toTime,
    installsScanned: errorGroups.length,
    erroringInstalls: errorGroups.length,
    diagnosticsOpened: opened,
    bugReports,
    summary,
    generatedAt: new Date().toISOString(),
  };
}

// ── MCP tool registration ─────────────────────────────────────────────────────

registerTool({
  name: 'lysnrai.keyboard.diagnose',
  description:
    'A2A pipeline: scans LysnrAI telemetry for error events from the iOS keyboard extension, groups errors by anonymousInstallId, opens a diagnostics session per affected install, and drafts a structured bug report. Returns severity-ranked reports with repro steps. Requires admin role.',
  requiredRole: 'admin',
  inputSchema: z.object({
    from: z.string().datetime().optional().describe('Start of telemetry window (default: 48h ago)'),
    to: z.string().datetime().optional().describe('End of telemetry window (default: now)'),
    minErrors: z.coerce
      .number()
      .int()
      .min(1)
      .default(3)
      .describe('Minimum error count per install to include in report (default 3)'),
  }),
  async execute(args, req) {
    const now = new Date();
    const toTime = args.to ?? now.toISOString();
    const fromTime = args.from ?? new Date(now.getTime() - 48 * 60 * 60 * 1000).toISOString();
    return runKeyboardDiagnosticsPipeline(fromTime, toTime, args.minErrors, req);
  },
});
