"""Suite 1 — Adversarial input tests (SQLi, NoSQL, XSS, CMDi, SSTI, XXE, LDAP, headers, polyglots, specials, auth bypass, tokens)."""

from __future__ import annotations
import asyncio
from typing import Any

import httpx

from .http_client import send_request
from .payloads import (
    SQL_INJECTIONS, SQL_INJECTIONS_QUICK,
    NOSQL_INJECTIONS, NOSQL_INJECTIONS_QUICK,
    XSS_PAYLOADS, XSS_PAYLOADS_QUICK,
    COMMAND_INJECTIONS, COMMAND_INJECTIONS_QUICK,
    SSTI_PAYLOADS, SSTI_PAYLOADS_QUICK,
    POLYGLOT_PAYLOADS,
    SPECIAL_STRINGS, SPECIAL_STRINGS_QUICK,
    HEADER_INJECTIONS, HEADER_INJECTIONS_QUICK,
    XXE_PAYLOADS,
    LDAP_INJECTIONS,
    AUTH_BYPASS_HEADERS,
    TOKEN_ADDRESS_PAYLOADS,
    OPEN_REDIRECT_PAYLOADS,
    SSRF_BODY_PAYLOADS,
    CACHE_POISON_HEADERS,
    GRAPHQL_PAYLOADS,
)

# CWE mapping per sub-category
_CWE = {
    "sqli": "CWE-89",
    "nosqli": "CWE-943",
    "xss": "CWE-79",
    "cmdi": "CWE-78",
    "ssti": "CWE-1336",
    "polyglot": "CWE-74",
    "special": "CWE-20",
    "header": "CWE-113",
    "xxe": "CWE-611",
    "ldap": "CWE-90",
    "auth_bypass": "CWE-287",
    "token": "CWE-20",
    "open_redirect": "CWE-601",
    "ssrf": "CWE-918",
    "cache_poison": "CWE-349",
    "graphql": "CWE-200",
}


async def run_adversarial_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] = []
    base_body = body if isinstance(body, dict) else {}
    quick = mode == "quick"

    # Build all tasks for parallel execution
    tasks: list[tuple[str, str, Any]] = []  # (label, sub_cat, coroutine_args)

    # --- SQLi ---
    for p in (SQL_INJECTIONS_QUICK if quick else SQL_INJECTIONS):
        tasks.append((f"SQLi: {p[:40]}", "sqli", _inject(base_body, p)))
    # --- NoSQLi ---
    for i, d in enumerate(NOSQL_INJECTIONS_QUICK if quick else NOSQL_INJECTIONS):
        op = list(d.keys())[0]
        tasks.append((f"NoSQLi: {op}", "nosqli", _inject_nosql(base_body, d)))
    # --- XSS ---
    for p in (XSS_PAYLOADS_QUICK if quick else XSS_PAYLOADS):
        tasks.append((f"XSS: {p[:40]}", "xss", _inject(base_body, p)))
    # --- CMDi ---
    for p in (COMMAND_INJECTIONS_QUICK if quick else COMMAND_INJECTIONS):
        tasks.append((f"CMDi: {p[:40]}", "cmdi", _inject(base_body, p)))
    # --- SSTI ---
    for p in (SSTI_PAYLOADS_QUICK if quick else SSTI_PAYLOADS):
        tasks.append((f"SSTI: {p[:40]}", "ssti", _inject(base_body, p)))
    # --- Polyglots (always run — they're the brutality) ---
    for p in POLYGLOT_PAYLOADS:
        tasks.append((f"Polyglot: {p[:35]}", "polyglot", _inject(base_body, p)))
    # --- Special strings ---
    for p in (SPECIAL_STRINGS_QUICK if quick else SPECIAL_STRINGS):
        tasks.append((f"Special: {repr(p[:25])}", "special", _inject(base_body, p)))
    # --- XXE ---
    if not quick:
        for p in XXE_PAYLOADS:
            tasks.append((f"XXE: {p[:40]}", "xxe", None))  # handled separately as raw_body
    # --- LDAP injection ---
    if not quick:
        for p in LDAP_INJECTIONS:
            tasks.append((f"LDAP: {p[:40]}", "ldap", _inject(base_body, p)))
    # --- Token/address edge cases ---
    token_payloads = TOKEN_ADDRESS_PAYLOADS[:10] if quick else TOKEN_ADDRESS_PAYLOADS
    for p in token_payloads:
        tasks.append((f"Token: {repr(p[:35])}", "token", _inject(base_body, p)))
    # --- Open redirect ---
    if not quick:
        for p in OPEN_REDIRECT_PAYLOADS:
            tasks.append((f"Redirect: {p[:35]}", "open_redirect", _inject(base_body, p)))
    # --- SSRF via body ---
    if not quick:
        for p in SSRF_BODY_PAYLOADS:
            tasks.append((f"SSRF: {p[:40]}", "ssrf", _inject(base_body, p)))
    # --- Header injection ---
    for hname, hval in (HEADER_INJECTIONS_QUICK if quick else HEADER_INJECTIONS):
        inj_hdrs = dict(headers or {})
        inj_hdrs[hname] = hval
        tasks.append((f"Header: {hname}: {hval[:20]}", "header", None))

    # Execute body-injection tests in parallel batches
    sem = asyncio.Semaphore(20)
    body_tasks = [(l, sc, b) for l, sc, b in tasks if sc not in ("header", "xxe")]
    xxe_tasks = [(l, sc, b) for l, sc, b in tasks if sc == "xxe"]

    async def _run_body(label, sub_cat, poisoned_body):
        async with sem:
            r = await send_request(client, url=url, method=method, headers=headers, body=poisoned_body)
            return _evaluate(label, r, sub_cat)

    coros = [_run_body(l, sc, b) for l, sc, b in body_tasks]
    results.extend(await asyncio.gather(*coros))

    # XXE tests — send as raw XML
    for i, p in enumerate(XXE_PAYLOADS if not quick else []):
        async with sem:
            r = await send_request(
                client, url=url, method=method, headers=headers,
                raw_body=p, content_type="application/xml",
            )
            results.append(_evaluate(f"XXE: payload_{i}", r, "xxe"))

    # Header injection tests
    for hname, hval in (HEADER_INJECTIONS_QUICK if quick else HEADER_INJECTIONS):
        inj_hdrs = dict(headers or {})
        inj_hdrs[hname] = hval
        r = await send_request(client, url=url, method=method, headers=inj_hdrs, body=body)
        results.append(_evaluate(f"Header: {hname}: {hval[:20]}", r, "header"))

    # Auth bypass tests — always run, they're critical
    auth_hdrs = AUTH_BYPASS_HEADERS[:6] if quick else AUTH_BYPASS_HEADERS
    for hname, hval in auth_hdrs:
        inj_hdrs = dict(headers or {})
        inj_hdrs[hname] = hval
        async with sem:
            r = await send_request(client, url=url, method=method, headers=inj_hdrs, body=body)
            code = r["status_code"]
            if code and code == 200:
                results.append(TestResult(
                    test=f"AuthBypass: {hname}: {hval[:25]}", status="CRASH",
                    detail=f"Server returned 200 with forged auth ({hval[:40]})",
                    latency_ms=r["latency_ms"], category="adversarial",
                    severity="CRITICAL", cwe="CWE-287",
                ))
            elif code and code >= 500:
                results.append(TestResult(
                    test=f"AuthBypass: {hname}: {hval[:25]}", status="CRASH",
                    detail=f"Server crashed on forged auth ({code})",
                    latency_ms=r["latency_ms"], category="adversarial",
                    severity="HIGH", cwe="CWE-287",
                ))
            else:
                results.append(TestResult(
                    test=f"AuthBypass: {hname}: {hval[:25]}", status="PASS",
                    detail=f"Rejected with {code}",
                    latency_ms=r["latency_ms"], category="adversarial",
                    severity="INFO", cwe="CWE-287",
                ))

    # Cache poisoning header tests
    if not quick:
        for hname, hval in CACHE_POISON_HEADERS:
            inj_hdrs = dict(headers or {})
            inj_hdrs[hname] = hval
            r = await send_request(client, url=url, method=method, headers=inj_hdrs, body=body)
            results.append(_evaluate(f"CachePoison: {hname}: {hval[:20]}", r, "cache_poison"))

    # GraphQL probing
    if not quick:
        from urllib.parse import urlparse
        parsed = urlparse(url)
        base = f"{parsed.scheme}://{parsed.netloc}"
        gql_urls = [base + "/graphql", base + "/graphql/", base + "/api/graphql", url]
        for gql_url in gql_urls:
            for p in GRAPHQL_PAYLOADS[:4]:
                r = await send_request(
                    client, url=gql_url, method="POST", headers=headers,
                    raw_body=p, content_type="application/json",
                )
                resp_body = r.get("body", "") or ""
                if "__schema" in resp_body or "__type" in resp_body or "data" in resp_body:
                    results.append(TestResult(
                        test=f"GraphQL: {gql_url[-30:]}",
                        status="WARN",
                        detail=f"GraphQL introspection enabled — schema exposed",
                        latency_ms=r["latency_ms"], category="adversarial",
                        severity="HIGH", cwe="CWE-200",
                    ))
                    break

    return results


def _inject(base: dict, payload: str) -> dict:
    if not base:
        return {"input": payload}
    return {k: payload for k in base}


def _inject_nosql(base: dict, operator: dict) -> dict:
    if not base:
        return {"input": operator}
    return {k: operator for k in base}


def _evaluate(test_name: str, r: dict, sub_cat: str):
    from .runner import TestResult

    code = r["status_code"]
    latency = r["latency_ms"]
    cwe = _CWE.get(sub_cat, "CWE-20")

    if r["error"] == "timeout":
        return TestResult(
            test=test_name, status="WARN", detail="Request timed out",
            latency_ms=latency, category="adversarial", severity="MEDIUM", cwe=cwe,
        )
    if code is None:
        return TestResult(
            test=test_name, status="WARN",
            detail=f"Connection error: {r['error'][:80]}" if r["error"] else "No response",
            latency_ms=latency, category="adversarial", severity="LOW", cwe=cwe,
        )
    if code >= 500:
        return TestResult(
            test=test_name, status="CRASH",
            detail=f"Server returned {code}",
            latency_ms=latency, category="adversarial", severity="CRITICAL", cwe=cwe,
        )
    if code == 200:
        return TestResult(
            test=test_name, status="WARN",
            detail="Server accepted malicious input (200 OK)",
            latency_ms=latency, category="adversarial", severity="HIGH", cwe=cwe,
        )
    return TestResult(
        test=test_name, status="PASS",
        detail=f"Rejected with {code}",
        latency_ms=latency, category="adversarial", severity="INFO", cwe=cwe,
    )
