"""Suite 2 — Protocol-level abuse tests."""

from __future__ import annotations
import asyncio
from typing import Any

import httpx

from .http_client import send_request
from .payloads import WRONG_CONTENT_TYPES, UNSUPPORTED_METHODS, INVALID_JSON_BODIES, INVALID_JSON_BODIES_QUICK


async def run_protocol_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"

    # --- Wrong Content-Type ---
    for ct in WRONG_CONTENT_TYPES:
        h = dict(headers or {})
        h["Content-Type"] = ct
        r = await send_request(client, url=url, method=method, headers=h, body=body)
        code = r["status_code"]
        if code and code >= 500:
            results.append(TestResult(
                test=f"Content-Type: {ct}", status="CRASH",
                detail=f"Server crashed on wrong Content-Type ({code})",
                latency_ms=r["latency_ms"], category="protocol",
                severity="HIGH", cwe="CWE-20",
            ))
        elif code and code == 200:
            results.append(TestResult(
                test=f"Content-Type: {ct}", status="WARN",
                detail="Server accepted wrong Content-Type without complaint",
                latency_ms=r["latency_ms"], category="protocol",
                severity="MEDIUM", cwe="CWE-20",
            ))
        else:
            results.append(TestResult(
                test=f"Content-Type: {ct}", status="PASS",
                detail=f"Rejected with {code}",
                latency_ms=r["latency_ms"], category="protocol",
                severity="INFO", cwe="CWE-20",
            ))

    # --- Unsupported HTTP methods ---
    for m in UNSUPPORTED_METHODS:
        r = await send_request(client, url=url, method=m, headers=headers, body=None)
        code = r["status_code"]
        if code and code >= 500:
            results.append(TestResult(
                test=f"Method: {m}", status="CRASH",
                detail=f"Server crashed on {m} ({code})",
                latency_ms=r["latency_ms"], category="protocol",
                severity="HIGH", cwe="CWE-749",
            ))
        elif code and code == 200:
            results.append(TestResult(
                test=f"Method: {m}", status="WARN",
                detail=f"Server accepted {m} request (possible misconfiguration)",
                latency_ms=r["latency_ms"], category="protocol",
                severity="MEDIUM", cwe="CWE-749",
            ))
        else:
            results.append(TestResult(
                test=f"Method: {m}", status="PASS",
                detail=f"Rejected with {code}",
                latency_ms=r["latency_ms"], category="protocol",
                severity="INFO", cwe="CWE-749",
            ))

    # --- Invalid JSON bodies ---
    invalid_bodies = INVALID_JSON_BODIES_QUICK if quick else INVALID_JSON_BODIES
    for idx, raw in enumerate(invalid_bodies):
        label = repr(raw[:30]) if len(raw) < 40 else repr(raw[:30]) + "…"
        r = await send_request(client, url=url, method=method, headers=headers, raw_body=raw)
        code = r["status_code"]
        if code and code >= 500:
            results.append(TestResult(
                test=f"Invalid body: {label}", status="CRASH",
                detail=f"Server crashed on malformed body ({code})",
                latency_ms=r["latency_ms"], category="protocol",
                severity="HIGH", cwe="CWE-20",
            ))
        elif code and code == 200:
            results.append(TestResult(
                test=f"Invalid body: {label}", status="WARN",
                detail="Accepted invalid body",
                latency_ms=r["latency_ms"], category="protocol",
                severity="MEDIUM", cwe="CWE-20",
            ))
        else:
            results.append(TestResult(
                test=f"Invalid body: {label}", status="PASS",
                detail=f"Rejected with {code}",
                latency_ms=r["latency_ms"], category="protocol",
                severity="INFO", cwe="CWE-20",
            ))

    # --- Oversized Content-Length ---
    h = dict(headers or {})
    h["Content-Length"] = "99999999"
    r = await send_request(client, url=url, method=method, headers=h, body=body)
    code = r["status_code"]
    if code and code >= 500:
        results.append(TestResult(
            test="Oversized Content-Length", status="CRASH",
            detail=f"Server crashed ({code})",
            latency_ms=r["latency_ms"], category="protocol",
            severity="HIGH", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test="Oversized Content-Length", status="PASS",
            detail=f"Handled with {code}",
            latency_ms=r["latency_ms"], category="protocol",
            severity="INFO", cwe="CWE-400",
        ))

    # --- Transfer-Encoding smuggling attempt ---
    h2 = dict(headers or {})
    h2["Transfer-Encoding"] = "chunked"
    h2["Content-Length"] = "0"
    r = await send_request(client, url=url, method=method, headers=h2, raw_body="0\r\n\r\n")
    code = r["status_code"]
    if code and code >= 500:
        results.append(TestResult(
            test="HTTP Smuggling (CL+TE)", status="CRASH",
            detail=f"Server crashed on conflicting CL/TE ({code})",
            latency_ms=r["latency_ms"], category="protocol",
            severity="CRITICAL", cwe="CWE-444",
        ))
    elif code and code == 200:
        results.append(TestResult(
            test="HTTP Smuggling (CL+TE)", status="WARN",
            detail="Server accepted conflicting CL/TE headers",
            latency_ms=r["latency_ms"], category="protocol",
            severity="HIGH", cwe="CWE-444",
        ))
    else:
        results.append(TestResult(
            test="HTTP Smuggling (CL+TE)", status="PASS",
            detail=f"Handled with {code}",
            latency_ms=r["latency_ms"], category="protocol",
            severity="INFO", cwe="CWE-444",
        ))

    # --- Duplicate headers abuse ---
    # httpx doesn't let us send truly duplicate headers easily,
    # but we can test duplicate parameter keys in the URL
    sep = "&" if "?" in url else "?"
    dup_url = f"{url}{sep}id=1&id=2&id=3"
    r = await send_request(client, url=dup_url, method=method, headers=headers, body=body)
    code = r["status_code"]
    if code and code >= 500:
        results.append(TestResult(
            test="HTTP Parameter Pollution", status="CRASH",
            detail=f"Server crashed on duplicate params ({code})",
            latency_ms=r["latency_ms"], category="protocol",
            severity="HIGH", cwe="CWE-235",
        ))
    else:
        results.append(TestResult(
            test="HTTP Parameter Pollution", status="PASS",
            detail=f"Handled with {code}",
            latency_ms=r["latency_ms"], category="protocol",
            severity="INFO", cwe="CWE-235",
        ))

    if quick:
        return results

    # --- Encoding confusion attacks ---
    encoding_tests = [
        ("Double-encoded slash", url + "/%252e%252e%252f"),
        ("Overlong UTF-8", url + "/%c0%af"),
        ("Backslash path", url + "/..\\..\\etc\\passwd"),
        ("Null byte in URL", url + "/%00"),
        ("Semicolon path param", url + ";jsessionid=fake"),
        ("Fragment abuse", url + "#<script>alert(1)</script>"),
        ("Tab in URL", url + "/\t/admin"),
        ("URL with unicode", url + "/%E3%80%82/admin"),
    ]
    for label, test_url in encoding_tests:
        r = await send_request(client, url=test_url, method="GET", headers=headers, body=None)
        code = r["status_code"]
        if code and code >= 500:
            results.append(TestResult(
                test=f"Encoding: {label}", status="CRASH",
                detail=f"Server crashed ({code})",
                latency_ms=r["latency_ms"], category="protocol",
                severity="HIGH", cwe="CWE-20",
            ))
        elif code and code == 200:
            results.append(TestResult(
                test=f"Encoding: {label}", status="WARN",
                detail=f"Server accepted encoded/mangled URL ({code})",
                latency_ms=r["latency_ms"], category="protocol",
                severity="MEDIUM", cwe="CWE-20",
            ))
        else:
            results.append(TestResult(
                test=f"Encoding: {label}", status="PASS",
                detail=f"Handled with {code}",
                latency_ms=r["latency_ms"], category="protocol",
                severity="INFO", cwe="CWE-20",
            ))

    # --- HTTP verb tunneling ---
    for override_method in ["PUT", "DELETE", "PATCH"]:
        h = dict(headers or {})
        h["X-HTTP-Method-Override"] = override_method
        r = await send_request(client, url=url, method="POST", headers=h, body=body)
        code = r["status_code"]
        if code and code == 200:
            results.append(TestResult(
                test=f"Verb tunnel: POST→{override_method}", status="WARN",
                detail=f"X-HTTP-Method-Override accepted — server executed {override_method}",
                latency_ms=r["latency_ms"], category="protocol",
                severity="MEDIUM", cwe="CWE-749",
            ))
        elif code and code >= 500:
            results.append(TestResult(
                test=f"Verb tunnel: POST→{override_method}", status="CRASH",
                detail=f"Server crashed on method override ({code})",
                latency_ms=r["latency_ms"], category="protocol",
                severity="HIGH", cwe="CWE-749",
            ))
        else:
            results.append(TestResult(
                test=f"Verb tunnel: POST→{override_method}", status="PASS",
                detail=f"Handled with {code}",
                latency_ms=r["latency_ms"], category="protocol",
                severity="INFO", cwe="CWE-749",
            ))

    # --- Content negotiation abuse ---
    accept_tests = [
        "text/html, application/xhtml+xml",
        "application/xml",
        "../../../etc/passwd",
        "*/*; q=0.1",
        "",
    ]
    for accept_val in accept_tests:
        h = dict(headers or {})
        h["Accept"] = accept_val
        r = await send_request(client, url=url, method=method, headers=h, body=body)
        code = r["status_code"]
        if code and code >= 500:
            results.append(TestResult(
                test=f"Accept header: {accept_val[:30]}", status="CRASH",
                detail=f"Server crashed on Accept header ({code})",
                latency_ms=r["latency_ms"], category="protocol",
                severity="HIGH", cwe="CWE-20",
            ))

    # --- Request smuggling: TE.CL variant ---
    h3 = dict(headers or {})
    h3["Transfer-Encoding"] = "chunked"
    smuggle_body = "0\r\n\r\nGET /admin HTTP/1.1\r\nHost: evil.com\r\n\r\n"
    r = await send_request(client, url=url, method=method, headers=h3, raw_body=smuggle_body)
    code = r["status_code"]
    if code and code >= 500:
        results.append(TestResult(
            test="HTTP Smuggling (TE.CL)", status="CRASH",
            detail=f"Server crashed on TE.CL smuggling attempt ({code})",
            latency_ms=r["latency_ms"], category="protocol",
            severity="CRITICAL", cwe="CWE-444",
        ))
    elif code and code == 200:
        results.append(TestResult(
            test="HTTP Smuggling (TE.CL)", status="WARN",
            detail="Server accepted TE.CL smuggling payload",
            latency_ms=r["latency_ms"], category="protocol",
            severity="HIGH", cwe="CWE-444",
        ))
    else:
        results.append(TestResult(
            test="HTTP Smuggling (TE.CL)", status="PASS",
            detail=f"Handled with {code}",
            latency_ms=r["latency_ms"], category="protocol",
            severity="INFO", cwe="CWE-444",
        ))

    # --- Massive query string ---
    sep2 = "&" if "?" in url else "?"
    giant_qs = url + sep2 + "&".join([f"p{i}={'A'*100}" for i in range(200)])
    r = await send_request(client, url=giant_qs, method="GET", headers=headers, body=None)
    code = r["status_code"]
    if code and code >= 500:
        results.append(TestResult(
            test="Giant query string (200 params)", status="CRASH",
            detail=f"Server crashed on massive query string ({code})",
            latency_ms=r["latency_ms"], category="protocol",
            severity="HIGH", cwe="CWE-400",
        ))
    else:
        results.append(TestResult(
            test="Giant query string (200 params)", status="PASS",
            detail=f"Handled with {code}",
            latency_ms=r["latency_ms"], category="protocol",
            severity="INFO", cwe="CWE-400",
        ))

    return results
