#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：test_performance_executor.py
模块描述：测试性能测试执行器与指标采集（增强指标体系）

覆盖指标：P95/标准差/Apdex/延迟/连接时间/字节数/峰值TPS/错误分类

作者：czj
创建日期：2026-08-20
"""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

import pytest
from app.executors.performance_executor import MetricsCollector


class TestStdDev:
    """测试总体标准差计算"""

    def test_less_than_two_values(self):
        assert MetricsCollector._std_dev([]) == 0.0
        assert MetricsCollector._std_dev([10.0]) == 0.0

    def test_constant_values(self):
        assert MetricsCollector._std_dev([5.0, 5.0, 5.0]) == 0.0

    def test_known_values(self):
        # 数据: 2, 4, 4, 4, 5, 5, 7, 9 → mean=5, variance=4, std=2.0
        data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]
        assert MetricsCollector._std_dev(data) == pytest.approx(2.0, abs=1e-6)


class TestApdex:
    """测试 Apdex 计算"""

    def test_empty_returns_one(self):
        assert MetricsCollector._apdex([]) == 1.0

    def test_all_satisfied(self):
        # 全部在 500ms 内 → 1.0
        times = [100.0, 200.0, 300.0, 500.0]
        assert MetricsCollector._apdex(times) == pytest.approx(1.0)

    def test_all_tolerating(self):
        # 全部在 500~2000ms 之间 → 0.5
        times = [600.0, 700.0, 800.0, 1000.0]
        assert MetricsCollector._apdex(times) == pytest.approx(0.5)

    def test_mixed(self):
        # 满意2 + 容忍1 + 超出1 → (2 + 1/2) / 4 = 0.625
        times = [100.0, 300.0, 800.0, 3000.0]
        assert MetricsCollector._apdex(times) == pytest.approx(0.625)

    def test_custom_threshold(self):
        times = [200.0, 400.0]
        assert MetricsCollector._apdex(times, threshold_ms=300) == pytest.approx(0.75)


class TestPercentile:
    """测试百分位数（线性插值）"""

    def test_single_value(self):
        assert MetricsCollector._percentile([42.0], 95) == pytest.approx(42.0)

    def test_empty(self):
        assert MetricsCollector._percentile([], 95) == 0.0

    def test_known_values(self):
        # 数据 [1, 2, 3, 4]：P50 = 2.5（线性插值）
        data = [1.0, 2.0, 3.0, 4.0]
        assert MetricsCollector._percentile(data, 50) == pytest.approx(2.5)
        # P100 = 4.0
        assert MetricsCollector._percentile(data, 100) == pytest.approx(4.0)

    def test_unsorted_input(self):
        data = [4.0, 1.0, 3.0, 2.0]
        assert MetricsCollector._percentile(data, 50) == pytest.approx(2.5)


class TestRecord:
    """测试 record() 指标记录与错误分类"""

    def make_collector(self):
        return MetricsCollector()

    def test_success_record(self):
        c = self.make_collector()
        c.record(status=200, elapsed_ms=100.0, success=True,
                 latency_ms=50.0, connect_ms=10.0, sent_bytes=100, received_bytes=500)
        assert c.total_requests == 1
        assert c.success_count == 1
        assert c.fail_count == 0
        assert c.all_latencies == [50.0]
        assert c.all_connect_times == [10.0]
        assert c.total_sent_bytes == 100
        assert c.total_received_bytes == 500
        assert c.status_2xx == 1

    def test_http_4xx_classification(self):
        c = self.make_collector()
        c.record(status=404, elapsed_ms=50.0, success=False, error='404 Not Found')
        assert c.status_4xx == 1
        assert c._error_counts["http_4xx"] == 1

    def test_http_5xx_classification(self):
        c = self.make_collector()
        c.record(status=500, elapsed_ms=50.0, success=False, error='500 Server Error')
        assert c.status_5xx == 1
        assert c._error_counts["http_5xx"] == 1

    def test_assertion_fail_classification(self):
        # 状态码成功但断言未通过 → assertion_fail
        c = self.make_collector()
        c.record(status=200, elapsed_ms=50.0, success=False, error='断言失败: 返回值不匹配')
        assert c._error_counts["assertion_fail"] == 1

    def test_timeout_classification(self):
        c = self.make_collector()
        c.record(status=0, elapsed_ms=3000.0, success=False, error='timeout', error_type='timeout')
        assert c._error_counts["timeout"] == 1

    def test_connect_error_classification(self):
        c = self.make_collector()
        c.record(status=0, elapsed_ms=1.0, success=False, error='connect error', error_type='connect_error')
        assert c._error_counts["connect_error"] == 1


class TestSnapshot:
    """测试 snapshot() 周期快照"""

    def test_empty_snapshot(self):
        c = MetricsCollector()
        snap = c.snapshot(peak_tps=3.5, concurrent_users=2)
        assert snap["elapsed"] >= 0
        assert snap["tps"] == 0
        assert snap["std_dev"] == 0
        assert snap["latency_avg"] == 0
        assert snap["connect_time_avg"] == 0
        assert snap["sent_bytes"] == 0
        assert snap["received_bytes"] == 0
        assert snap["peak_tps"] == 3.5
        assert snap["concurrent_users"] == 2

    def test_snapshot_fields_and_reset(self):
        c = MetricsCollector()
        c.record(status=200, elapsed_ms=100.0, success=True, latency_ms=40.0,
                 connect_ms=5.0, sent_bytes=200, received_bytes=1000)
        c.record(status=200, elapsed_ms=300.0, success=True, latency_ms=120.0,
                 connect_ms=15.0, sent_bytes=200, received_bytes=1000)

        snap = c.snapshot(peak_tps=1.0, concurrent_users=1)
        assert snap["tps"] == 2.0
        # 平均响应时间 (100+300)/2
        assert snap["avg_response_time"] == pytest.approx(200.0)
        # 标准差 (100-200)^2 + (300-200)^2 → /2 → sqrt(10000) = 100
        assert snap["std_dev"] == pytest.approx(100.0)
        assert snap["latency_avg"] == pytest.approx(80.0)
        assert snap["connect_time_avg"] == pytest.approx(10.0)
        assert snap["sent_bytes"] == 400
        assert snap["received_bytes"] == 2000
        assert snap["p95"] == pytest.approx(290.0)

        # 周期计数器已重置
        assert c._period_success == 0
        assert c._period_times == []
        assert c._period_sent_bytes == 0
        # 全局累计保留
        assert c.total_requests == 2
        assert c.total_sent_bytes == 400

    def test_snapshot_multiple_periods(self):
        c = MetricsCollector()
        c.record(status=200, elapsed_ms=100.0, success=True)
        c.snapshot()
        c.record(status=200, elapsed_ms=200.0, success=True)
        snap = c.snapshot()
        assert snap["tps"] == 1.0
        assert snap["avg_response_time"] == pytest.approx(200.0)
        assert c.total_requests == 2


class TestSummary:
    """测试 summary() 最终汇总"""

    def test_empty_summary(self):
        c = MetricsCollector()
        s = c.summary()
        assert s["total_requests"] == 0
        assert s["error_rate"] == 0.0
        assert s["p95_response_time"] == 0
        assert s["std_dev"] == 0
        assert s["apdex"] == pytest.approx(1.0)
        assert s["error_type_timeout"] == 0

    def test_summary_aggregation(self):
        c = MetricsCollector()
        # 100ms 成功 + 600ms 成功 + 5000ms 超时
        c.record(status=200, elapsed_ms=100.0, success=True, latency_ms=40.0, connect_ms=5.0,
                 sent_bytes=100, received_bytes=1000)
        c.record(status=200, elapsed_ms=600.0, success=True, latency_ms=200.0, connect_ms=20.0,
                 sent_bytes=100, received_bytes=2000)
        c.record(status=0, elapsed_ms=5000.0, success=False, error='timeout', error_type='timeout',
                 latency_ms=5000.0, connect_ms=10.0, sent_bytes=100, received_bytes=0)

        s = c.summary()
        assert s["total_requests"] == 3
        assert s["success_count"] == 2
        assert s["fail_count"] == 1
        assert s["error_rate"] == pytest.approx(100 / 3, abs=0.01)
        assert s["p50_response_time"] == pytest.approx(600.0)
        # 线性插值 P95（数据 [100, 600, 5000]）：600 + (5000-600) * 0.9
        assert s["p95_response_time"] == pytest.approx(4560.0)
        assert s["latency_avg"] == pytest.approx((40 + 200 + 5000) / 3, abs=0.01)
        assert s["connect_time_avg"] == pytest.approx((5 + 20 + 10) / 3, abs=0.01)
        assert s["total_sent_bytes"] == 300
        assert s["total_received_bytes"] == 3000
        assert s["error_type_timeout"] == 1
        # Apdex：满意1(100) + 容忍1(600) + 超出1(5000) → (1 + 0.5) / 3 = 0.5
        assert s["apdex"] == pytest.approx(0.5, abs=0.01)

    def test_summary_error_types(self):
        c = MetricsCollector()
        c.record(status=500, elapsed_ms=100.0, success=False, error='500')
        c.record(status=404, elapsed_ms=100.0, success=False, error='404')
        c.record(status=200, elapsed_ms=100.0, success=False, error='断言失败')
        c.record(status=0, elapsed_ms=100.0, success=False, error='conn', error_type='connect_error')
        s = c.summary()
        assert s["error_type_http_5xx"] == 1
        assert s["error_type_http_4xx"] == 1
        assert s["error_type_assertion"] == 1
        assert s["error_type_connect_error"] == 1
        assert s["fail_count"] == 4

    def test_bytes_kb_per_sec(self):
        c = MetricsCollector()
        c.record(status=200, elapsed_ms=100.0, success=True, sent_bytes=10240, received_bytes=5120)
        s = c.summary()
        # elapsed = max(实际耗时, 1s)，测试进程远小于 1s → 速率为 10/1 = 10 KB/s；
        # 慢机器上耗时略超 1s 时速率相应变小，故断言上界
        assert s["sent_kb_per_sec"] > 0
        assert s["sent_kb_per_sec"] <= 10.0
        assert s["received_kb_per_sec"] > 0
        assert s["received_kb_per_sec"] <= 5.0