/**
 * SyncDiagnosticsAgent — A2A pipeline for PeakPulse sync failure diagnostics.
 *
 * Agent roster (4 steps):
 *   1. SyncFailureInspectorAgent  — stats + recent failure count from telemetry
 *   2. DiagnosticsSessionAgent    — create platform diagnostics session (conditional)
 *   3. SyncRetryObserverAgent     — collect current logs + traces from session (best-effort)
 *   4. SyncDiagnosticReportAgent  — assemble report + optional extraction entity run
 *
 * MCP tools:
 *   peakpulse.sync.diagnose(userId, deviceId?, errorCode?, platform?) — 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,
  diagnosticsGetLogs,
  diagnosticsGetTraces,
  diagnosticsUpdateSession,
  type DebugSession,
} from '../../lib/platform-client.js';
import { extractionRun, type ExtractionItem } from '../../lib/extraction-client.js';

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

type FailurePattern = 'transient' | 'auth' | 'persistent' | 'unknown';

interface SyncFailureInspection {
  userId: string;
  deviceId: string | null;
  queueDepth: number;
  lastSuccessfulSync: string | null;
  failurePattern: FailurePattern;
  recentFailureCount: number;
  shouldStartDiagnostics: boolean;
}

interface DiagnosticsCapture {
  skipped: boolean;
  skipReason?: string;
  session?: DebugSession;
}

interface ObserverCapture {
  networkTraces: unknown[];
  logEntries: unknown[];
  syncAttemptDetected: boolean;
  timedOut: boolean;
}

export interface SyncDiagnosticReport {
  runId: string;
  productId: 'peakpulse';
  userId: string;
  deviceId: string | null;
  failurePattern: FailurePattern;
  diagnosticsSessionId: string | null;
  queueDepth: number;
  recentFailureCount: number;
  syncAttemptDetected: boolean;
  rootCauseSummary: string;
  recommendedAction: string;
  extractedEntities: ExtractionItem[];
  generatedAt: string;
}

// ── Step 1: SyncFailureInspectorAgent ────────────────────────────────────────

async function inspectSyncFailures(
  userId: string,
  deviceId: string | null,
  errorCode: string | null,
  platform: string | null,
  opts: { token?: string; requestId?: string }
): Promise<SyncFailureInspection> {
  let lastSuccessfulSync: string | null = null;

  // Query telemetry for recent sync failures (last 24h)
  const now = new Date();
  const from = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString();
  let recentFailureCount = 0;
  try {
    const result = await telemetryQuery(
      {
        productId: 'peakpulse',
        eventType: 'sync_upload_failed',
        from,
        to: now.toISOString(),
        limit: 20,
      },
      { ...opts, productId: 'peakpulse' }
    );
    recentFailureCount = result.total;
    // Derive last successful sync from most recent event timestamp (inverse: last non-failure event)
    const events = result.events as Array<Record<string, unknown>>;
    if (events.length > 0 && recentFailureCount === 0) {
      lastSuccessfulSync = (events[0]?.['timestamp'] as string) ?? null;
    }
  } catch {
    // best-effort
  }

  // queueDepth: approximate as recent failure count (each failure = one upload that didn't land)
  const queueDepth = recentFailureCount;

  // Classify failure pattern
  let failurePattern: FailurePattern;
  if (errorCode === 'auth_401' || errorCode === 'unauthorized') {
    failurePattern = 'auth';
  } else if (recentFailureCount >= 5) {
    failurePattern = 'persistent';
  } else if (recentFailureCount <= 1) {
    failurePattern = 'transient';
  } else {
    failurePattern = 'unknown';
  }

  const shouldStartDiagnostics =
    recentFailureCount >= 2 || failurePattern === 'auth' || failurePattern === 'persistent';

  return {
    userId,
    deviceId,
    queueDepth,
    lastSuccessfulSync,
    failurePattern,
    recentFailureCount,
    shouldStartDiagnostics,
  };
}

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

async function startDiagnosticsSession(
  inspection: SyncFailureInspection,
  opts: { token?: string; requestId?: string }
): Promise<DiagnosticsCapture> {
  if (!inspection.shouldStartDiagnostics) {
    return { skipped: true, skipReason: 'Single transient failure — diagnostics not warranted' };
  }

  const collectionLevel = inspection.failurePattern === 'auth' ? 'trace' : 'debug';

  try {
    const session = await diagnosticsCreateSession(
      {
        productId: 'peakpulse',
        targetUserId: inspection.userId,
        ...(inspection.deviceId ? { targetAnonymousId: inspection.deviceId } : {}),
        collectionLevel,
        captureLogs: true,
        captureNetwork: true,
        maxDurationMinutes: 15,
      },
      opts
    );
    return { skipped: false, session };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    return { skipped: true, skipReason: `Failed to create diagnostics session: ${msg}` };
  }
}

// ── Step 3: SyncRetryObserverAgent (snapshot — no long-poll) ─────────────────

async function captureSessionData(
  sessionId: string,
  opts: { token?: string; requestId?: string }
): Promise<ObserverCapture> {
  let networkTraces: unknown[] = [];
  let logEntries: unknown[] = [];

  try {
    const [logsResult, tracesResult] = await Promise.all([
      diagnosticsGetLogs(sessionId, { limit: 50 }, opts),
      diagnosticsGetTraces(sessionId, { limit: 50 }, opts),
    ]);
    logEntries = logsResult.logs;
    networkTraces = tracesResult.traces;
  } catch {
    // best-effort
  }

  const syncAttemptDetected = networkTraces.length > 0 || logEntries.length > 0;

  return { networkTraces, logEntries, syncAttemptDetected, timedOut: false };
}

// ── Step 4: SyncDiagnosticReportAgent ────────────────────────────────────────

function deriveRootCause(inspection: SyncFailureInspection, observer: ObserverCapture): string {
  if (inspection.failurePattern === 'auth') {
    return `Authentication failure pattern detected (${inspection.recentFailureCount} failures in 24h). Token may be expired or revoked.`;
  }
  if (inspection.failurePattern === 'persistent' && inspection.queueDepth > 0) {
    return `Persistent sync failure with ${inspection.queueDepth} session(s) queued. Likely network or server-side conflict.`;
  }
  if (inspection.failurePattern === 'transient') {
    return `Single transient failure — likely a momentary network interruption. No action needed.`;
  }
  if (!observer.syncAttemptDetected) {
    return `No sync attempt captured in diagnostics window — device may not have retried yet.`;
  }
  return `Sync failure detected with ${inspection.recentFailureCount} occurrences. Review network traces for root cause.`;
}

function deriveRecommendedAction(inspection: SyncFailureInspection): string {
  if (inspection.failurePattern === 'auth')
    return 'Force a token refresh on the device — auth credentials are stale.';
  if (inspection.queueDepth > 10)
    return 'Investigate Cosmos DB conflict resolution — high queue depth suggests repeated write conflicts.';
  if (inspection.failurePattern === 'transient')
    return 'Monitor for recurrence — single failure, no action required now.';
  return 'Retry sync on WiFi; if failure persists, check platform-service Cosmos write throughput.';
}

async function buildReport(
  runId: string,
  inspection: SyncFailureInspection,
  diagnosticsCapture: DiagnosticsCapture,
  observer: ObserverCapture,
  opts: { requestId?: string }
): Promise<SyncDiagnosticReport> {
  // Close diagnostics session
  if (diagnosticsCapture.session) {
    try {
      await diagnosticsUpdateSession(diagnosticsCapture.session.id, { status: 'completed' }, opts);
    } catch {
      // best-effort
    }
  }

  // Optional: extract entities from log text
  let extractedEntities: ExtractionItem[] = [];
  const logText = (observer.logEntries as Array<Record<string, unknown>>)
    .slice(0, 10)
    .map(l => String(l['message'] ?? l['msg'] ?? ''))
    .filter(Boolean)
    .join('\n');

  if (logText.length > 20) {
    try {
      const result = await extractionRun(
        { text: logText, taskId: 'triage' },
        { requestId: opts.requestId }
      );
      extractedEntities = result.extractions.filter(e =>
        ['error_code', 'url', 'status_code', 'entity'].includes(e.extraction_class)
      );
    } catch {
      // best-effort
    }
  }

  return {
    runId,
    productId: 'peakpulse',
    userId: inspection.userId,
    deviceId: inspection.deviceId,
    failurePattern: inspection.failurePattern,
    diagnosticsSessionId: diagnosticsCapture.session?.id ?? null,
    queueDepth: inspection.queueDepth,
    recentFailureCount: inspection.recentFailureCount,
    syncAttemptDetected: observer.syncAttemptDetected,
    rootCauseSummary: deriveRootCause(inspection, observer),
    recommendedAction: deriveRecommendedAction(inspection),
    extractedEntities,
    generatedAt: new Date().toISOString(),
  };
}

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

export async function runSyncDiagnosticsPipeline(
  userId: string,
  deviceId: string | null,
  errorCode: string | null,
  platform: string | null,
  opts: { token?: string; requestId?: string }
): Promise<SyncDiagnosticReport> {
  const runId = `run_syncd_${randomUUID().slice(0, 8)}`;

  // Step 1: inspect
  const inspection = await inspectSyncFailures(userId, deviceId, errorCode, platform, opts);

  // Step 2: diagnostics session (conditional)
  const diagnosticsCapture = await startDiagnosticsSession(inspection, opts);

  // Step 3: capture logs/traces (if session created)
  let observer: ObserverCapture = {
    networkTraces: [],
    logEntries: [],
    syncAttemptDetected: false,
    timedOut: false,
  };
  if (diagnosticsCapture.session) {
    observer = await captureSessionData(diagnosticsCapture.session.id, opts);
  }

  // Step 4: build report
  return buildReport(runId, inspection, diagnosticsCapture, observer, {
    requestId: opts.requestId,
  });
}

// ── MCP tool: peakpulse.sync.diagnose ────────────────────────────────────────

registerTool({
  name: 'peakpulse.sync.diagnose',
  description: [
    'A2A sync diagnostics pipeline for PeakPulse.',
    'Step 1: SyncFailureInspectorAgent — queries current session queue depth + recent sync_upload_failed',
    'telemetry events to classify the failure pattern (transient/auth/persistent/unknown).',
    'Step 2: DiagnosticsSessionAgent — creates a platform diagnostics session targeting the device',
    'if recentFailureCount >= 2 or pattern is auth/persistent.',
    'Step 3: SyncRetryObserverAgent — collects current logs + network traces from the session.',
    'Step 4: SyncDiagnosticReportAgent — assembles a structured report with rootCauseSummary,',
    'recommendedAction, and optional extraction entities from log text.',
    'Requires admin role.',
  ].join(' '),
  requiredRole: 'admin',
  inputSchema: z.object({
    userId: z.string().min(1).describe('PeakPulse user ID'),
    deviceId: z
      .string()
      .optional()
      .describe('Device ID (anonymousInstallId) — narrows diagnostics scope'),
    errorCode: z
      .string()
      .optional()
      .describe(
        'Error code from the failure event (e.g. network_timeout, auth_401, cosmos_conflict)'
      ),
    platform: z.enum(['ios', 'android']).optional().describe('Platform of the failing device'),
  }),
  async execute(args, req: McpToolRequest) {
    const token = req.headers.authorization?.slice(7);
    return runSyncDiagnosticsPipeline(
      args.userId,
      args.deviceId ?? null,
      args.errorCode ?? null,
      args.platform ?? null,
      { token, requestId: req.id }
    );
  },
});
