"""Suite 3 — Load / stress / spike tests."""

from __future__ import annotations
import asyncio
import statistics
import time
from typing import Any

import httpx

from .http_client import send_request


async def run_load_tests(
    client: httpx.AsyncClient,
    url: str,
    method: str,
    headers: dict[str, str] | None,
    body: Any | None,
    mode: str = "full",
) -> list:
    from .runner import TestResult

    results: list[TestResult] = []
    quick = mode == "quick"

    # ── Concurrent burst ──
    concurrent = 10 if quick else 50
    sem = asyncio.Semaphore(concurrent)

    async def _one():
        async with sem:
            return await send_request(client, url=url, method=method, headers=headers, body=body)

    t0 = time.perf_counter()
    responses = await asyncio.gather(*[_one() for _ in range(concurrent)])
    burst_dur = (time.perf_counter() - t0) * 1000

    errors = [r for r in responses if r["status_code"] is None or r["status_code"] >= 500]
    latencies = [r["latency_ms"] for r in responses if r["latency_ms"] > 0]

    if len(errors) > concurrent * 0.5:
        results.append(TestResult(
            test=f"Concurrent burst ({concurrent} req)",
            status="CRASH",
            detail=f"{len(errors)}/{concurrent} requests failed — server cannot handle concurrency",
            latency_ms=burst_dur, category="load",
            severity="CRITICAL", cwe="CWE-400",
        ))
    elif len(errors) > 0:
        results.append(TestResult(
            test=f"Concurrent burst ({concurrent} req)",
            status="WARN",
            detail=f"{len(errors)}/{concurrent} requests failed",
            latency_ms=burst_dur, category="load",
            severity="HIGH", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test=f"Concurrent burst ({concurrent} req)",
            status="PASS",
            detail=f"All {concurrent} requests succeeded",
            latency_ms=burst_dur, category="load",
            severity="INFO", cwe="CWE-400",
        ))

    # ── Latency percentiles ──
    if latencies:
        p50 = statistics.median(latencies)
        p95 = latencies[int(len(latencies) * 0.95)] if len(latencies) >= 20 else max(latencies)
        mn = min(latencies)
        mx = max(latencies)
        results.append(TestResult(
            test="Latency profile",
            status="WARN" if p95 > 2000 else "PASS",
            detail=f"p50={p50:.0f}ms  p95={p95:.0f}ms  min={mn:.0f}ms  max={mx:.0f}ms",
            latency_ms=p95, category="load",
            severity="MEDIUM" if p95 > 2000 else "INFO", cwe="CWE-400",
        ))

    if quick:
        return results

    # ── Full mode extras ──

    # Spike test: 5 rapid batches of 20
    spike_errors = 0
    for _ in range(5):
        batch = await asyncio.gather(*[
            send_request(client, url=url, method=method, headers=headers, body=body)
            for _ in range(20)
        ])
        spike_errors += sum(1 for r in batch if r["status_code"] is None or r["status_code"] >= 500)
        await asyncio.sleep(0.05)

    if spike_errors > 30:
        results.append(TestResult(
            test="Spike test (5 × 20 req)",
            status="CRASH",
            detail=f"{spike_errors}/100 requests failed under spike load",
            latency_ms=0, category="load",
            severity="CRITICAL", cwe="CWE-400",
        ))
    elif spike_errors > 5:
        results.append(TestResult(
            test="Spike test (5 × 20 req)",
            status="WARN",
            detail=f"{spike_errors}/100 requests failed",
            latency_ms=0, category="load",
            severity="HIGH", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test="Spike test (5 × 20 req)",
            status="PASS",
            detail=f"{spike_errors}/100 failures — spike resistance OK",
            latency_ms=0, category="load",
            severity="INFO", cwe="CWE-400",
        ))

    # Sequential endurance: 40 requests back-to-back, track degradation
    seq_latencies: list[float] = []
    seq_errors = 0
    for _ in range(40):
        r = await send_request(client, url=url, method=method, headers=headers, body=body)
        if r["status_code"] and r["status_code"] < 500:
            seq_latencies.append(r["latency_ms"])
        else:
            seq_errors += 1

    if seq_latencies:
        first_10 = statistics.mean(seq_latencies[:10]) if len(seq_latencies) >= 10 else seq_latencies[0]
        last_10 = statistics.mean(seq_latencies[-10:]) if len(seq_latencies) >= 10 else seq_latencies[-1]
        degradation = ((last_10 - first_10) / first_10 * 100) if first_10 > 0 else 0

        if degradation > 100:
            results.append(TestResult(
                test="Sequential endurance (40 req)",
                status="WARN",
                detail=f"Latency degraded {degradation:.0f}% over 40 requests ({first_10:.0f}ms → {last_10:.0f}ms)",
                latency_ms=last_10, category="load",
                severity="MEDIUM", cwe="CWE-400",
            ))
        else:
            results.append(TestResult(
                test="Sequential endurance (40 req)",
                status="PASS",
                detail=f"Stable performance ({first_10:.0f}ms → {last_10:.0f}ms, {degradation:+.0f}%)",
                latency_ms=last_10, category="load",
                severity="INFO", cwe="CWE-400",
            ))

    if seq_errors > 10:
        results.append(TestResult(
            test="Sequential error rate",
            status="CRASH",
            detail=f"{seq_errors}/40 sequential requests failed",
            latency_ms=0, category="load",
            severity="HIGH", cwe="CWE-400",
        ))

    # Connection churn: rapidly open/close connections
    churn_errors = 0
    for _ in range(10):
        async with httpx.AsyncClient(timeout=httpx.Timeout(5.0), verify=False) as tmp:
            r = await send_request(tmp, url=url, method=method, headers=headers, body=body)
            if r["status_code"] is None or r["status_code"] >= 500:
                churn_errors += 1

    if churn_errors > 3:
        results.append(TestResult(
            test="Connection churn (10 fresh conn)",
            status="WARN",
            detail=f"{churn_errors}/10 failed under connection churn",
            latency_ms=0, category="load",
            severity="MEDIUM", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test="Connection churn (10 fresh conn)",
            status="PASS",
            detail="Handles rapid new connections",
            latency_ms=0, category="load",
            severity="INFO", cwe="CWE-400",
        ))

    # ── Slowloris-style: slow drip requests ──
    slow_errors = 0
    for _ in range(5):
        try:
            async with httpx.AsyncClient(timeout=httpx.Timeout(15.0), verify=False) as slow_client:
                r = await send_request(
                    slow_client, url=url, method=method, headers=headers,
                    raw_body="a" * 10,  # tiny body
                )
                if r["status_code"] is None or r["status_code"] >= 500:
                    slow_errors += 1
        except Exception:
            slow_errors += 1

    if slow_errors > 2:
        results.append(TestResult(
            test="Slow drip requests (5 slow sends)",
            status="WARN",
            detail=f"{slow_errors}/5 failed — possible slowloris vulnerability",
            latency_ms=0, category="load",
            severity="MEDIUM", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test="Slow drip requests (5 slow sends)",
            status="PASS",
            detail="Handles slow clients gracefully",
            latency_ms=0, category="load",
            severity="INFO", cwe="CWE-400",
        ))

    # ── Payload size escalation: 1KB → 100KB → 1MB ──
    size_results = []
    for label, size in [("1KB", 1024), ("100KB", 102400), ("1MB", 1048576)]:
        mega_body = {"data": "X" * size}
        r = await send_request(client, url=url, method=method, headers=headers, body=mega_body)
        code = r["status_code"]
        if code and code >= 500:
            size_results.append((label, "CRASH", code))
        elif code is None:
            size_results.append((label, "FAIL", None))
        else:
            size_results.append((label, "OK", code))

    crashes = sum(1 for _, s, _ in size_results if s == "CRASH")
    if crashes > 0:
        details = ", ".join(f"{l}={s}({c})" for l, s, c in size_results)
        results.append(TestResult(
            test="Payload size escalation",
            status="CRASH",
            detail=f"Server crashed on large payloads: {details}",
            latency_ms=0, category="load",
            severity="HIGH", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test="Payload size escalation",
            status="PASS",
            detail="Handled 1KB/100KB/1MB payloads",
            latency_ms=0, category="load",
            severity="INFO", cwe="CWE-400",
        ))

    # ── Thundering herd: 100 simultaneous requests ──
    herd_results = await asyncio.gather(*[
        send_request(client, url=url, method=method, headers=headers, body=body)
        for _ in range(100)
    ])
    herd_errors = sum(1 for r in herd_results if r["status_code"] is None or (r["status_code"] and r["status_code"] >= 500))
    if herd_errors > 30:
        results.append(TestResult(
            test="Thundering herd (100 simultaneous)",
            status="CRASH",
            detail=f"{herd_errors}/100 failed — catastrophic under mass concurrency",
            latency_ms=0, category="load",
            severity="CRITICAL", cwe="CWE-400",
        ))
    elif herd_errors > 10:
        results.append(TestResult(
            test="Thundering herd (100 simultaneous)",
            status="WARN",
            detail=f"{herd_errors}/100 failed under mass concurrency",
            latency_ms=0, category="load",
            severity="HIGH", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test="Thundering herd (100 simultaneous)",
            status="PASS",
            detail=f"{herd_errors}/100 failures — handles thundering herd",
            latency_ms=0, category="load",
            severity="INFO", cwe="CWE-400",
        ))

    return results
