import { build, Histogram } from 'hdr-histogram-js';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';

import { RunOptions } from '../src';
import { TressiConfig } from '../src/config';
import { Distribution } from '../src/distribution';
import { Runner } from '../src/runner';
import { generateMarkdownReport, generateSummary } from '../src/summarizer';

const createHistogram = (latencies: number[]): Histogram => {
  const histogram = build();
  for (const l of latencies) {
    histogram.recordValue(l);
  }
  return histogram;
};

const mockRunner = {
  getSampledResults: () => [
    {
      method: 'GET',
      url: 'http://a.com',
      latencyMs: 100,
      status: 200,
      success: true,
      timestamp: 1,
    },
    {
      method: 'GET',
      url: 'http://a.com',
      latencyMs: 150,
      status: 200,
      success: true,
      timestamp: 2,
    },
    {
      method: 'GET',
      url: 'http://b.com',
      latencyMs: 200,
      status: 200,
      success: true,
      timestamp: 3,
    },
    {
      method: 'GET',
      url: 'http://b.com',
      latencyMs: 500,
      status: 500,
      success: false,
      error: 'Error',
      timestamp: 4,
    },
  ],
  getHistogram: () => createHistogram([100, 150, 200, 500]),
  getEndpointHistograms: () => {
    const map = new Map<string, Histogram>();
    map.set('GET http://a.com', createHistogram([100, 150]));
    map.set('GET http://b.com', createHistogram([200, 500]));
    return map;
  },
  getStatusCodeMap: () => ({ 200: 3, 500: 1 }),
  getSuccessfulRequestsCount: () => 3,
  getFailedRequestsCount: () => 1,
  getAverageLatency: () => 237.5,
  getDistribution: () => {
    const distribution = new Distribution();
    [100, 150, 200, 500].forEach((l) => distribution.add(l));
    return distribution;
  },
  getSuccessfulRequestsByEndpoint: () => {
    const map = new Map<string, number>();
    map.set('GET http://a.com', 2);
    map.set('GET http://b.com', 1);
    return map;
  },
  getFailedRequestsByEndpoint: () => {
    const map = new Map<string, number>();
    map.set('GET http://a.com', 0);
    map.set('GET http://b.com', 1);
    return map;
  },
} as unknown as Runner;

const mockOptions: RunOptions = {
  config: { requests: [] },
  durationSec: 10,
  rps: 1, // 10 requests total theoretical
};

const mockConfig: TressiConfig = {
  requests: [{ url: 'http://a.com', method: 'GET' }],
};

/**
 * Test suite for the summary generation logic.
 */
describe('summarizer', () => {
  beforeAll(() => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2023-01-01T03:00:00.000Z'));
  });

  afterAll(() => {
    vi.useRealTimers();
  });

  /**
   * It should correctly calculate all global statistics for a given set of results.
   */
  it('should generate an accurate global summary', () => {
    const summary = generateSummary(mockRunner, mockOptions, 10);
    const { global: g } = summary;

    expect(g.totalRequests).toBe(4);
    expect(g.successfulRequests).toBe(3);
    expect(g.failedRequests).toBe(1);
    expect(g.avgLatencyMs).toBeCloseTo(237.5);
    expect(g.minLatencyMs).toBe(100);
    expect(g.maxLatencyMs).toBe(500);
    expect(g.p95LatencyMs).toBe(500);
    expect(g.actualRps).toBe(0.4);
    expect(g.theoreticalMaxRps).toBeCloseTo(1);
    expect(g.achievedPercentage).toBeCloseTo(40);
  });

  /**
   * It should correctly aggregate results by URL and calculate statistics for each endpoint.
   */
  it('should generate an accurate summary for each endpoint', () => {
    const summary = generateSummary(mockRunner, mockOptions);
    const { endpoints: e } = summary;

    expect(e).toHaveLength(2);

    const summaryA = e.find((s) => s.url === 'http://a.com');
    expect(summaryA).toBeDefined();
    expect(summaryA?.method).toBe('GET');
    expect(summaryA?.totalRequests).toBe(2);
    expect(summaryA?.successfulRequests).toBe(2);
    expect(summaryA?.failedRequests).toBe(0);
    expect(summaryA?.avgLatencyMs).toBe(125);
    expect(summaryA?.p95LatencyMs).toBe(150);

    const summaryB = e.find((s) => s.url === 'http://b.com');
    expect(summaryB).toBeDefined();
    expect(summaryB?.method).toBe('GET');
    expect(summaryB?.totalRequests).toBe(2);
    expect(summaryB?.successfulRequests).toBe(1);
    expect(summaryB?.failedRequests).toBe(1);
    expect(summaryB?.avgLatencyMs).toBe(350);
    expect(summaryB?.p95LatencyMs).toBe(500);
  });

  /**
   * It should generate a Markdown report containing all summary sections.
   */
  it('should generate a comprehensive Markdown report', () => {
    const summary = generateSummary(mockRunner, mockOptions, 10);
    const metadata = {
      exportName: 'my-test-report',
      runDate: new Date(),
    };
    const markdown = generateMarkdownReport(
      summary,
      mockOptions,
      mockRunner,
      mockConfig,
      metadata,
    );

    expect(markdown).toMatchSnapshot();
  });
});
