import { describe, expect, test } from 'bun:test';

import {
  applySessionEventActivity,
  applyAssistantUsageEvent,
  applyTurnEventLog,
  buildAgentActivityRows,
  formatAgentActivityLabel,
  formatDuration,
  formatNanoAiu,
  formatTokenCount,
  isAgentActivityActive,
  isAgentActivityCompleted,
  pruneSessionActivities,
  pruneSessionRequestUsage,
  type SessionActivityMap,
  type SessionRequestUsageMap,
} from '@renderer/lib/sessionActivity';
import type { PatternDefinition } from '@shared/domain/pattern';
import type { SessionEventRecord } from '@shared/domain/event';

describe('session activity helpers', () => {
  const agents: PatternDefinition['agents'] = [
    {
      id: 'architect',
      name: 'Architect',
      description: 'Designs the system.',
      instructions: 'Think about architecture.',
      model: 'gpt-5.4',
      reasoningEffort: 'high',
    },
    {
      id: 'reviewer',
      name: 'Reviewer',
      description: 'Reviews the solution.',
      instructions: 'Review the work.',
      model: 'gpt-5.4',
      reasoningEffort: 'medium',
    },
  ];

  test('stores activity per session and per agent', () => {
    const architectEvent: SessionEventRecord = {
      sessionId: 'session-1',
      kind: 'agent-activity',
      occurredAt: '2026-03-23T00:00:00.000Z',
      activityType: 'thinking',
      agentId: 'architect',
      agentName: 'Architect',
    };
    const reviewerEvent: SessionEventRecord = {
      sessionId: 'session-1',
      kind: 'agent-activity',
      occurredAt: '2026-03-23T00:00:01.000Z',
      activityType: 'tool-calling',
      agentId: 'reviewer',
      agentName: 'Reviewer',
      toolName: 'read_file',
    };

    expect(applySessionEventActivity({}, architectEvent)).toEqual({
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'thinking',
        },
      },
    });

    expect(
      applySessionEventActivity(
        applySessionEventActivity({}, architectEvent),
        reviewerEvent,
      ),
    ).toEqual({
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'thinking',
        },
        reviewer: {
          agentId: 'reviewer',
          agentName: 'Reviewer',
          activityType: 'tool-calling',
          toolName: 'read_file',
        },
      },
    });
  });

  test('warns when an agent-activity event is missing identifiers', () => {
    const originalWarn = console.warn;
    const warnings: unknown[][] = [];
    console.warn = (...args: unknown[]) => {
      warnings.push(args);
    };

    try {
      const current: SessionActivityMap = {
        'session-1': {
          architect: {
            agentId: 'architect',
            agentName: 'Architect',
            activityType: 'thinking',
          },
        },
      };

      expect(
        applySessionEventActivity(current, {
          sessionId: 'session-1',
          kind: 'agent-activity',
          occurredAt: '2026-03-23T00:00:00.000Z',
          activityType: 'thinking',
          agentId: '   ',
          agentName: '   ',
        }),
      ).toBe(current);

      expect(warnings).toHaveLength(1);
      expect(String(warnings[0][0])).toContain('Dropping agent-activity event');
    } finally {
      console.warn = originalWarn;
    }
  });

  test('clears stale activity when a session restarts', () => {
    const current: SessionActivityMap = {
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'thinking',
        },
      },
      'session-2': {
        reviewer: {
          agentId: 'reviewer',
          agentName: 'Reviewer',
          activityType: 'handoff',
        },
      },
    };

    expect(
      applySessionEventActivity(current, {
        sessionId: 'session-1',
        kind: 'status',
        occurredAt: '2026-03-23T00:00:00.000Z',
        status: 'running',
      }),
    ).toEqual({
      'session-2': current['session-2'],
    });
  });

  test('preserves completed activity on idle and error', () => {
    const current: SessionActivityMap = {
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'completed',
        },
      },
    };

    expect(
      applySessionEventActivity(current, {
        sessionId: 'session-1',
        kind: 'status',
        occurredAt: '2026-03-23T00:00:00.000Z',
        status: 'idle',
      }),
    ).toEqual(current);

    expect(
      applySessionEventActivity(current, {
        sessionId: 'session-1',
        kind: 'error',
        occurredAt: '2026-03-23T00:00:01.000Z',
        error: 'Boom',
      }),
    ).toEqual(current);
  });

  test('clears only active agent states when a session becomes idle', () => {
    const current: SessionActivityMap = {
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'completed',
        },
        reviewer: {
          agentId: 'reviewer',
          agentName: 'Reviewer',
          activityType: 'thinking',
        },
      },
    };

    expect(
      applySessionEventActivity(current, {
        sessionId: 'session-1',
        kind: 'status',
        occurredAt: '2026-03-23T00:00:01.000Z',
        status: 'idle',
      }),
    ).toEqual({
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'completed',
        },
      },
    });
  });

  test('clears only active agent states when a run is cancelled', () => {
    const current: SessionActivityMap = {
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'completed',
        },
        reviewer: {
          agentId: 'reviewer',
          agentName: 'Reviewer',
          activityType: 'thinking',
        },
      },
    };

    expect(
      applySessionEventActivity(current, {
        sessionId: 'session-1',
        kind: 'run-updated',
        occurredAt: '2026-03-23T00:00:01.000Z',
        run: {
          id: 'run-1',
          requestId: 'turn-1',
          projectId: 'project-1',
          projectPath: 'C:\\workspace\\project',
          workspaceKind: 'project',
          patternId: 'pattern-1',
          patternName: 'Pattern',
          patternMode: 'single',
          triggerMessageId: 'msg-1',
          startedAt: '2026-03-23T00:00:00.000Z',
          completedAt: '2026-03-23T00:00:01.000Z',
          status: 'cancelled',
          agents: [],
          events: [],
        },
      }),
    ).toEqual({
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'completed',
        },
      },
    });
  });

  test('builds rows for all agents with sensible defaults', () => {
    expect(buildAgentActivityRows(undefined, agents)).toEqual([
      {
        key: 'architect',
        agentName: 'Architect',
      },
      {
        key: 'reviewer',
        agentName: 'Reviewer',
      },
    ]);

    expect(
      buildAgentActivityRows(
        {
          architect: {
            agentId: 'architect',
            agentName: 'Architect',
            activityType: 'completed',
          },
          reviewer: {
            agentId: 'reviewer',
            agentName: 'Reviewer',
            activityType: 'tool-calling',
            toolName: 'read_file',
          },
        },
        agents,
      ),
    ).toEqual([
      {
        key: 'architect',
        agentName: 'Architect',
        activity: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'completed',
        },
      },
      {
        key: 'reviewer',
        agentName: 'Reviewer',
        activity: {
          agentId: 'reviewer',
          agentName: 'Reviewer',
          activityType: 'tool-calling',
          toolName: 'read_file',
        },
      },
    ]);
  });

  test('formats contextual activity labels and state flags', () => {
    expect(formatAgentActivityLabel(undefined)).toBe('No status yet');
    expect(
      formatAgentActivityLabel({
        agentId: 'reviewer',
        agentName: 'Reviewer',
        activityType: 'tool-calling',
        toolName: 'read_file',
      }),
    ).toBe('Using read_file…');
    expect(
      formatAgentActivityLabel({
        agentId: 'reviewer',
        agentName: 'Reviewer',
        activityType: 'handoff',
      }),
    ).toBe('Handling handoff…');
    expect(
      formatAgentActivityLabel({
        agentId: 'reviewer',
        agentName: 'Reviewer',
        activityType: 'completed',
      }),
    ).toBe('Completed');
    expect(
      isAgentActivityActive({
        agentId: 'architect',
        agentName: 'Architect',
        activityType: 'thinking',
      }),
    ).toBe(true);
    expect(
      isAgentActivityCompleted({
        agentId: 'architect',
        agentName: 'Architect',
        activityType: 'completed',
      }),
    ).toBe(true);
    expect(isAgentActivityCompleted(undefined)).toBe(false);
  });

  test('prunes activity state for sessions that no longer exist', () => {
    const current: SessionActivityMap = {
      'session-1': {
        architect: {
          agentId: 'architect',
          agentName: 'Architect',
          activityType: 'thinking',
        },
      },
      'session-2': {
        reviewer: {
          agentId: 'reviewer',
          agentName: 'Reviewer',
          activityType: 'tool-calling',
          toolName: 'read_file',
        },
      },
    };

    expect(pruneSessionActivities(current, ['session-2'])).toEqual({
      'session-2': current['session-2'],
    });
  });
});

describe('assistant usage accumulator', () => {
  function makeUsageEvent(overrides: Partial<SessionEventRecord> = {}): SessionEventRecord {
    return {
      sessionId: 'session-1',
      kind: 'assistant-usage',
      occurredAt: '2026-03-23T00:00:00.000Z',
      agentId: 'architect',
      agentName: 'Architect',
      usageModel: 'gpt-5.4',
      usageInputTokens: 1200,
      usageOutputTokens: 300,
      usageCost: 0.42,
      usageDuration: 8200,
      usageTotalNanoAiu: 1_200_000_000,
      ...overrides,
    };
  }

  test('accumulates session-level totals from assistant-usage events', () => {
    let state: SessionRequestUsageMap = {};
    state = applyAssistantUsageEvent(state, makeUsageEvent());
    state = applyAssistantUsageEvent(state, makeUsageEvent({
      usageInputTokens: 800,
      usageOutputTokens: 200,
      usageCost: 0.28,
      usageDuration: 5000,
      usageTotalNanoAiu: 2_400_000_000,
    }));

    const usage = state['session-1']!;
    expect(usage.requestCount).toBe(2);
    expect(usage.totalInputTokens).toBe(2000);
    expect(usage.totalOutputTokens).toBe(500);
    expect(usage.totalCost).toBeCloseTo(0.70);
    expect(usage.totalDurationMs).toBe(13200);
    expect(usage.totalNanoAiu).toBe(2_400_000_000);
  });

  test('accumulates per-agent totals keyed by agentId', () => {
    let state: SessionRequestUsageMap = {};
    state = applyAssistantUsageEvent(state, makeUsageEvent({ agentId: 'architect' }));
    state = applyAssistantUsageEvent(state, makeUsageEvent({
      agentId: 'reviewer',
      agentName: 'Reviewer',
      usageInputTokens: 500,
      usageOutputTokens: 100,
      usageCost: 0.10,
      usageDuration: 3000,
    }));
    state = applyAssistantUsageEvent(state, makeUsageEvent({
      agentId: 'architect',
      usageInputTokens: 600,
      usageOutputTokens: 150,
      usageCost: 0.20,
      usageDuration: 4000,
    }));

    const usage = state['session-1']!;
    expect(usage.perAgent['architect']!.requestCount).toBe(2);
    expect(usage.perAgent['architect']!.inputTokens).toBe(1800);
    expect(usage.perAgent['reviewer']!.requestCount).toBe(1);
    expect(usage.perAgent['reviewer']!.inputTokens).toBe(500);
  });

  test('ignores non-assistant-usage events', () => {
    const state: SessionRequestUsageMap = {};
    const result = applyAssistantUsageEvent(state, {
      sessionId: 'session-1',
      kind: 'session-usage',
      occurredAt: '2026-03-23T00:00:00.000Z',
      tokenLimit: 100000,
      currentTokens: 5000,
    });
    expect(result).toBe(state);
  });

  test('stores latest quota snapshots', () => {
    const snapshots = {
      premium_interactions: {
        entitlementRequests: 50,
        usedRequests: 12,
        remainingPercentage: 76,
        overage: 0,
        overageAllowedWithExhaustedQuota: true,
        resetDate: '2026-04-01T00:00:00Z',
      },
    };

    let state: SessionRequestUsageMap = {};
    state = applyAssistantUsageEvent(state, makeUsageEvent({ usageQuotaSnapshots: snapshots }));

    expect(state['session-1']!.latestQuotaSnapshots).toEqual(snapshots);
  });

  test('prunes request usage for removed sessions', () => {
    const current: SessionRequestUsageMap = {
      'session-1': {
        totalInputTokens: 1000,
        totalOutputTokens: 200,
        totalCost: 0.3,
        totalDurationMs: 5000,
        totalNanoAiu: 1_000_000_000,
        requestCount: 1,
        perAgent: {},
      },
      'session-2': {
        totalInputTokens: 500,
        totalOutputTokens: 100,
        totalCost: 0.1,
        totalDurationMs: 2000,
        totalNanoAiu: 500_000_000,
        requestCount: 1,
        perAgent: {},
      },
    };

    const pruned = pruneSessionRequestUsage(current, ['session-2']);
    expect(pruned).toEqual({ 'session-2': current['session-2'] });
    expect(pruned).not.toBe(current);
  });
});

describe('usage formatting helpers', () => {
  test('formatTokenCount formats values at different scales', () => {
    expect(formatTokenCount(500)).toBe('500');
    expect(formatTokenCount(1200)).toBe('1.2k');
    expect(formatTokenCount(45300)).toBe('45.3k');
    expect(formatTokenCount(1_500_000)).toBe('1.5M');
  });

  test('formatNanoAiu converts nano-AIU to human-readable', () => {
    expect(formatNanoAiu(420_000_000)).toBe('0.42');
    expect(formatNanoAiu(12_300_000_000)).toBe('12.3');
    expect(formatNanoAiu(150_000_000_000)).toBe('150');
  });

  test('formatDuration formats milliseconds', () => {
    expect(formatDuration(8200)).toBe('8.2s');
    expect(formatDuration(150_000)).toBe('2.5m');
  });
});

describe('workflow diagnostic turn events', () => {
  function makeDiagnosticEvent(overrides: Partial<SessionEventRecord> = {}): SessionEventRecord {
    return {
      sessionId: 'session-1',
      kind: 'workflow-diagnostic',
      occurredAt: '2026-03-23T00:00:00.000Z',
      diagnosticSeverity: 'error',
      diagnosticKind: 'executor-failed',
      diagnosticMessage: 'Tool crashed.',
      ...overrides,
    };
  }

  test('formats executor-failed with full metadata', () => {
    const result = applyTurnEventLog({}, makeDiagnosticEvent({
      executorId: 'Primary',
      exceptionType: 'InvalidOperationException',
    }));
    const entries = result['session-1']!;
    expect(entries).toHaveLength(1);
    expect(entries[0].label).toBe('Executor failed');
    expect(entries[0].detail).toBe('Primary · InvalidOperationException · Tool crashed.');
    expect(entries[0].success).toBe(false);
  });

  test('formats workflow-warning with message only', () => {
    const result = applyTurnEventLog({}, makeDiagnosticEvent({
      diagnosticSeverity: 'warning',
      diagnosticKind: 'workflow-warning',
      diagnosticMessage: 'Token budget is nearly exhausted.',
      executorId: undefined,
      exceptionType: undefined,
    }));
    const entries = result['session-1']!;
    expect(entries[0].label).toBe('Workflow warning');
    expect(entries[0].detail).toBe('Token budget is nearly exhausted.');
    expect(entries[0].success).toBeUndefined();
  });

  test('formats subworkflow-error with subworkflow ID', () => {
    const result = applyTurnEventLog({}, makeDiagnosticEvent({
      diagnosticKind: 'subworkflow-error',
      subworkflowId: 'subworkflow-review',
      exceptionType: 'InvalidOperationException',
      diagnosticMessage: 'Reviewer agent failed.',
    }));
    const entries = result['session-1']!;
    expect(entries[0].label).toBe('Subworkflow error');
    expect(entries[0].detail).toBe('subworkflow-review · InvalidOperationException · Reviewer agent failed.');
  });

  test('formats workflow-error without optional fields', () => {
    const result = applyTurnEventLog({}, makeDiagnosticEvent({
      diagnosticKind: 'workflow-error',
      diagnosticMessage: 'Workflow terminated unexpectedly.',
      executorId: undefined,
      subworkflowId: undefined,
      exceptionType: undefined,
    }));
    const entries = result['session-1']!;
    expect(entries[0].label).toBe('Workflow error');
    expect(entries[0].detail).toBe('Workflow terminated unexpectedly.');
    expect(entries[0].success).toBe(false);
  });

  test('falls back to severity when diagnosticKind is missing', () => {
    const result = applyTurnEventLog({}, makeDiagnosticEvent({
      diagnosticKind: undefined,
      diagnosticSeverity: 'warning',
      diagnosticMessage: 'Something odd happened.',
    }));
    const entries = result['session-1']!;
    expect(entries[0].label).toBe('Workflow warning');
  });
});
