"""Suite 5 — Consistency / idempotency / stability tests."""

from __future__ import annotations
import asyncio
from typing import Any

import httpx

from .http_client import send_request


async def run_consistency_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"
    iterations = 5 if quick else 15

    # ── Idempotency: same request N times, check all return same status ──
    codes: list[int | None] = []
    bodies_hash: list[int] = []
    for _ in range(iterations):
        r = await send_request(client, url=url, method=method, headers=headers, body=body)
        codes.append(r["status_code"])
        bodies_hash.append(hash(r.get("body", "")))

    unique_codes = set(codes)
    if None in unique_codes:
        unique_codes.discard(None)
        failures = codes.count(None)
    else:
        failures = 0

    if failures > iterations * 0.3:
        results.append(TestResult(
            test=f"Idempotency ({iterations} repeats)",
            status="CRASH",
            detail=f"{failures}/{iterations} requests failed — unreliable endpoint",
            latency_ms=0, category="consistency",
            severity="HIGH", cwe="CWE-754",
        ))
    elif len(unique_codes) > 1:
        results.append(TestResult(
            test=f"Idempotency ({iterations} repeats)",
            status="WARN",
            detail=f"Inconsistent status codes: {unique_codes}",
            latency_ms=0, category="consistency",
            severity="MEDIUM", cwe="CWE-754",
        ))
    else:
        results.append(TestResult(
            test=f"Idempotency ({iterations} repeats)",
            status="PASS",
            detail=f"Consistent {unique_codes.pop() if unique_codes else 'N/A'} across {iterations} requests",
            latency_ms=0, category="consistency",
            severity="INFO", cwe="CWE-754",
        ))

    # ── Body consistency ──
    unique_bodies = set(bodies_hash)
    if len(unique_bodies) > 1:
        results.append(TestResult(
            test="Response body consistency",
            status="WARN",
            detail=f"{len(unique_bodies)} distinct response bodies across {iterations} identical requests",
            latency_ms=0, category="consistency",
            severity="LOW", cwe="CWE-754",
        ))
    else:
        results.append(TestResult(
            test="Response body consistency",
            status="PASS",
            detail="Response body stable across all repeats",
            latency_ms=0, category="consistency",
            severity="INFO", cwe="CWE-754",
        ))

    if quick:
        return results

    # ── Full mode: Race condition (parallel identical writes) ──
    if method.upper() in ("POST", "PUT", "PATCH"):
        race_results = await asyncio.gather(*[
            send_request(client, url=url, method=method, headers=headers, body=body)
            for _ in range(20)
        ])
        race_codes = [r["status_code"] for r in race_results]
        race_5xx = sum(1 for c in race_codes if c and c >= 500)
        race_none = sum(1 for c in race_codes if c is None)

        if race_5xx + race_none > 5:
            results.append(TestResult(
                test="Race condition (20 parallel writes)",
                status="CRASH",
                detail=f"{race_5xx} server errors, {race_none} connection failures",
                latency_ms=0, category="consistency",
                severity="HIGH", cwe="CWE-362",
            ))
        elif race_5xx + race_none > 0:
            results.append(TestResult(
                test="Race condition (20 parallel writes)",
                status="WARN",
                detail=f"{race_5xx + race_none}/20 issues under parallel writes",
                latency_ms=0, category="consistency",
                severity="MEDIUM", cwe="CWE-362",
            ))
        else:
            results.append(TestResult(
                test="Race condition (20 parallel writes)",
                status="PASS",
                detail="No issues with 20 parallel write requests",
                latency_ms=0, category="consistency",
                severity="INFO", cwe="CWE-362",
            ))

    # ── Header casing sensitivity ──
    h1 = dict(headers or {})
    h1["accept"] = "application/json"
    h2 = dict(headers or {})
    h2["ACCEPT"] = "application/json"
    r1 = await send_request(client, url=url, method=method, headers=h1, body=body)
    r2 = await send_request(client, url=url, method=method, headers=h2, body=body)
    if r1["status_code"] != r2["status_code"]:
        results.append(TestResult(
            test="Header casing sensitivity",
            status="WARN",
            detail=f"Different responses for 'accept' vs 'ACCEPT': {r1['status_code']} vs {r2['status_code']}",
            latency_ms=0, category="consistency",
            severity="LOW", cwe="CWE-178",
        ))
    else:
        results.append(TestResult(
            test="Header casing sensitivity",
            status="PASS",
            detail="Header casing handled consistently",
            latency_ms=0, category="consistency",
            severity="INFO", cwe="CWE-178",
        ))

    # ── URL casing sensitivity ──
    import re as _re
    lower_url = url.lower()
    upper_url = _re.sub(r'(https?://[^/]+)(.*)', lambda m: m.group(1) + m.group(2).upper(), url)
    r1 = await send_request(client, url=lower_url, method=method, headers=headers, body=body)
    r2 = await send_request(client, url=upper_url, method=method, headers=headers, body=body)
    if r1["status_code"] != r2["status_code"]:
        results.append(TestResult(
            test="URL casing sensitivity",
            status="WARN",
            detail=f"Different responses for lowercase vs uppercase path: {r1['status_code']} vs {r2['status_code']}",
            latency_ms=0, category="consistency",
            severity="LOW", cwe="CWE-178",
        ))
    else:
        results.append(TestResult(
            test="URL casing sensitivity",
            status="PASS",
            detail="URL casing handled consistently",
            latency_ms=0, category="consistency",
            severity="INFO", cwe="CWE-178",
        ))

    # ── Trailing slash consistency ──
    url_slash = url.rstrip("/") + "/"
    url_noslash = url.rstrip("/")
    r1 = await send_request(client, url=url_slash, method=method, headers=headers, body=body)
    r2 = await send_request(client, url=url_noslash, method=method, headers=headers, body=body)
    if r1["status_code"] != r2["status_code"]:
        results.append(TestResult(
            test="Trailing slash consistency",
            status="WARN",
            detail=f"Different responses with/without trailing slash: {r1['status_code']} vs {r2['status_code']}",
            latency_ms=0, category="consistency",
            severity="LOW", cwe="CWE-20",
        ))
    else:
        results.append(TestResult(
            test="Trailing slash consistency",
            status="PASS",
            detail="Trailing slash handled consistently",
            latency_ms=0, category="consistency",
            severity="INFO", cwe="CWE-20",
        ))

    # ── Timing consistency (detect timing oracle) ──
    import time
    timings_valid: list[float] = []
    timings_invalid: list[float] = []
    for _ in range(5):
        t0 = time.perf_counter()
        await send_request(client, url=url, method=method, headers=headers, body=body)
        timings_valid.append((time.perf_counter() - t0) * 1000)

        t0 = time.perf_counter()
        await send_request(client, url=url, method=method, headers=headers, body={"invalid_key": "x" * 1000})
        timings_invalid.append((time.perf_counter() - t0) * 1000)

    avg_valid = sum(timings_valid) / len(timings_valid)
    avg_invalid = sum(timings_invalid) / len(timings_invalid)
    timing_diff = abs(avg_valid - avg_invalid)
    if timing_diff > 500:
        results.append(TestResult(
            test="Timing oracle detection",
            status="WARN",
            detail=f"Significant timing difference: valid={avg_valid:.0f}ms vs invalid={avg_invalid:.0f}ms (Δ{timing_diff:.0f}ms)",
            latency_ms=0, category="consistency",
            severity="MEDIUM", cwe="CWE-208",
        ))
    else:
        results.append(TestResult(
            test="Timing oracle detection",
            status="PASS",
            detail=f"Timing consistent: valid={avg_valid:.0f}ms vs invalid={avg_invalid:.0f}ms (Δ{timing_diff:.0f}ms)",
            latency_ms=0, category="consistency",
            severity="INFO", cwe="CWE-208",
        ))

    # ── Error response consistency ──
    error_payloads = [
        {"input": "' OR '1'='1"},
        {"input": "<script>alert(1)</script>"},
        {"input": "$(whoami)"},
    ]
    error_codes = set()
    for ep in error_payloads:
        r = await send_request(client, url=url, method=method, headers=headers, body=ep)
        if r["status_code"]:
            error_codes.add(r["status_code"])
    if len(error_codes) > 2:
        results.append(TestResult(
            test="Error response consistency",
            status="WARN",
            detail=f"Different error codes for similar bad input: {error_codes}",
            latency_ms=0, category="consistency",
            severity="MEDIUM", cwe="CWE-209",
        ))
    else:
        results.append(TestResult(
            test="Error response consistency",
            status="PASS",
            detail=f"Error codes consistent: {error_codes}",
            latency_ms=0, category="consistency",
            severity="INFO", cwe="CWE-209",
        ))

    return results
