import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { McpLogger } from '../tools/types.js';

// ── Mock platform-client before importing runner ───────────────────────────
vi.mock('../../lib/platform-client.js', () => ({
  telemetryClusters: vi.fn(),
  diagnosticsCreateSession: vi.fn(),
  runsCreate: vi.fn(),
  runsUpdate: vi.fn(),
  runStepsCreate: vi.fn(),
  runStepsUpdate: vi.fn(),
}));

import {
  telemetryClusters,
  diagnosticsCreateSession,
  runsCreate,
  runsUpdate,
  runStepsCreate,
  runStepsUpdate,
} from '../../lib/platform-client.js';
import { runIncidentPipeline } from './runner.js';
import type { SupportIncidentBrief } from './types.js';

const mockLog: McpLogger = {
  info: vi.fn(),
  warn: vi.fn(),
  error: vi.fn(),
  debug: vi.fn(),
};

const baseBrief: SupportIncidentBrief = {
  productId: 'lysnrai',
  userReport: { summary: 'Dictation inserts nothing in Messages', platform: 'ios' },
  timeWindow: {},
  openDiagnosticsSession: false,
};

const mockClusters = [
  {
    id: 'abc123:202503',
    pk: 'lysnrai:ios:keyboard',
    fingerprint: 'abc123',
    severity: 'error' as const,
    totalCount: 42,
    firstSeenAt: '2026-03-01T00:00:00Z',
    lastSeenAt: '2026-03-05T10:00:00Z',
    platform: 'ios',
    module: 'keyboard',
    eventName: 'insertText.failed',
    sampleMessage: 'insertText returned noop',
    status: 'open' as const,
  },
];

beforeEach(() => {
  vi.clearAllMocks();
  vi.mocked(telemetryClusters).mockResolvedValue({
    clusters: mockClusters,
    total: mockClusters.length,
  });
  vi.mocked(diagnosticsCreateSession).mockResolvedValue({
    id: 'sess_001',
    status: 'active',
    expiresAt: '2026-03-05T11:00:00Z',
    collectionLevel: 'debug',
  } as never);
  vi.mocked(runsCreate).mockResolvedValue({} as never);
  vi.mocked(runsUpdate).mockResolvedValue({} as never);
  vi.mocked(runStepsCreate).mockResolvedValue({} as never);
  vi.mocked(runStepsUpdate).mockResolvedValue({} as never);
});

describe('A2A incident pipeline', () => {
  it('runs dispatcher → telemetry analyst → report writer (no diagnostics)', async () => {
    const report = await runIncidentPipeline(baseBrief, {
      log: mockLog,
      requestId: 'req_test',
    });

    expect(report.runId).toMatch(/^run_/);
    expect(report.productId).toBe('lysnrai');
    expect(report.clusterCount).toBe(1);
    expect(report.clusters[0].clusterId).toBe('abc123:202503');
    expect(report.diagnosticsSession).toBeNull();
    expect(report.markdownReport).toContain('abc123:202503');
    expect(report.topHypotheses.length).toBeGreaterThan(0);
    expect(report.recommendedActions.length).toBeGreaterThan(0);
    expect(diagnosticsCreateSession).not.toHaveBeenCalled();
  });

  it('creates a diagnostics session when openDiagnosticsSession is true + userId provided', async () => {
    const brief: SupportIncidentBrief = {
      ...baseBrief,
      userReport: { ...baseBrief.userReport, userId: 'usr_abc' },
      openDiagnosticsSession: true,
    };

    const report = await runIncidentPipeline(brief, { log: mockLog });

    expect(diagnosticsCreateSession).toHaveBeenCalledOnce();
    expect(report.diagnosticsSession?.id).toBe('sess_001');
    expect(report.markdownReport).toContain('sess_001');
  });

  it('skips diagnostics session when no userId/anonymousInstallId', async () => {
    const brief: SupportIncidentBrief = { ...baseBrief, openDiagnosticsSession: true };
    const report = await runIncidentPipeline(brief, { log: mockLog });

    expect(diagnosticsCreateSession).not.toHaveBeenCalled();
    expect(report.diagnosticsSession).toBeNull();
  });

  it('handles telemetry query failure gracefully', async () => {
    vi.mocked(telemetryClusters).mockRejectedValue(new Error('upstream timeout'));

    const report = await runIncidentPipeline(baseBrief, { log: mockLog });

    expect(report.clusterCount).toBe(0);
    expect(report.markdownReport).toContain('upstream timeout');
  });

  it('logs runId + stepId on every step', async () => {
    await runIncidentPipeline(baseBrief, { log: mockLog });

    const infoCalls = vi.mocked(mockLog.info).mock.calls;
    const stepNames = infoCalls.map(([obj]) => (obj as Record<string, unknown>).a2aStep);
    expect(stepNames).toContain('pipeline.start');
    expect(stepNames).toContain('dispatcher.complete');
    expect(stepNames).toContain('telemetry_analyst.complete');
    expect(stepNames).toContain('pipeline.complete');
  });

  it('persists the incident pipeline run and steps through platform-service', async () => {
    await runIncidentPipeline(baseBrief, {
      log: mockLog,
      token: 'jwt_1',
      requestId: 'req_runs',
      initiatedBy: 'admin_1',
    });

    expect(runsCreate).toHaveBeenCalledOnce();
    expect(runStepsCreate).toHaveBeenCalledTimes(3);
    expect(runStepsUpdate).toHaveBeenCalledTimes(3);
    expect(runsUpdate).toHaveBeenCalledWith(
      expect.stringMatching(/^run_/),
      expect.objectContaining({ status: 'succeeded' }),
      expect.objectContaining({
        token: 'jwt_1',
        requestId: 'req_runs',
        productId: 'lysnrai',
      })
    );
  });
});

describe('dispatcher', () => {
  it('defaults time window to last 24h when not provided', async () => {
    const before = Date.now();
    const report = await runIncidentPipeline(baseBrief, { log: mockLog });
    const after = Date.now();

    const clusterCall = vi.mocked(telemetryClusters).mock.calls[0][0] as Record<string, string>;
    const fromTs = new Date(clusterCall.from).getTime();
    const toTs = new Date(clusterCall.to).getTime();

    expect(toTs).toBeGreaterThanOrEqual(before);
    expect(toTs).toBeLessThanOrEqual(after + 100);
    expect(toTs - fromTs).toBeGreaterThanOrEqual(24 * 60 * 60 * 1000 - 100);
    expect(report).toBeDefined();
  });
});
