class TestPlannerAgent:
    """Deterministic API test planner (advanced).

    Generates a broad suite of test cases per endpoint and ensures at least ~40
    tests when there are enough fields to mutate.

    Categories covered:
    - happy path
    - method misuse
    - auth/header tampering
    - missing/null/empty/type/boundary
    - security payload probes
    """

    SECURITY_PAYLOADS = [
        "' OR 1=1 --",
        "\" OR \"\"=\"",
        "<script>alert(1)</script>",
        "../../etc/passwd",
        "${jndi:ldap://127.0.0.1/a}",
    ]

    COMMON_METHOD_MISUSE = ["GET", "POST", "PUT", "PATCH", "DELETE"]

    def plan(self, request: dict):
        method = request["method"].upper()
        params = request.get("params", {}) or {}
        body = request.get("body", {}) or {}
        headers = request.get("headers", {}) or {}

        tests: list[dict] = []

        # 0) Happy path
        tests.append({"name": "happy_path", "mutation": None})

        # Generic tests (apply to most APIs)
        tests.append({
            "name": "headers_empty",
            "mutation": {
                "target": "headers",
                "path": [],
                "operation": "REPLACE",
                "original_value": headers,
                "new_value": {},
                "strategy": "headers",
            },
        })
        tests.append({
            "name": "headers_accept_text",
            "mutation": {
                "target": "headers",
                "path": [next((k for k in headers.keys() if k.lower() == "accept"), "accept")],
                "operation": "REPLACE",
                "original_value": headers.get(next((k for k in headers.keys() if k.lower() == "accept"), "accept")),
                "new_value": "text/plain",
                "strategy": "headers",
            },
        })

        # 1) Method misuse (expect 405/4xx, depends on API)
        for m in self.COMMON_METHOD_MISUSE:
            if m == method:
                continue
            tests.append({
                "name": f"wrong_method_{m}",
                "mutation": {
                    "target": "method",
                    "path": [],
                    "operation": "REPLACE",
                    "original_value": method,
                    "new_value": m,
                    "strategy": "method_misuse",
                },
            })

        # 2) Header tampering / auth tests
        # If caller provides an Authorization header, test missing/invalid.
        auth_key = None
        for k in headers.keys():
            if k.lower() == "authorization":
                auth_key = k
                break

        if auth_key:
            tests.append({
                "name": "auth_missing",
                "mutation": {
                    "target": "headers",
                    "path": [auth_key],
                    "operation": "REMOVE",
                    "original_value": headers.get(auth_key),
                    "new_value": None,
                    "strategy": "auth",
                },
            })
            tests.append({
                "name": "auth_invalid",
                "mutation": {
                    "target": "headers",
                    "path": [auth_key],
                    "operation": "REPLACE",
                    "original_value": headers.get(auth_key),
                    "new_value": "Bearer invalid.invalid.invalid",
                    "strategy": "auth",
                },
            })

        # Content-Type hardening (for body methods)
        if method != "GET":
            # Remove content-type
            ct_key = None
            for k in headers.keys():
                if k.lower() == "content-type":
                    ct_key = k
                    break
            if ct_key:
                tests.append({
                    "name": "content_type_missing",
                    "mutation": {
                        "target": "headers",
                        "path": [ct_key],
                        "operation": "REMOVE",
                        "original_value": headers.get(ct_key),
                        "new_value": None,
                        "strategy": "headers",
                    },
                })
                tests.append({
                    "name": "content_type_invalid",
                    "mutation": {
                        "target": "headers",
                        "path": [ct_key],
                        "operation": "REPLACE",
                        "original_value": headers.get(ct_key),
                        "new_value": "text/plain",
                        "strategy": "headers",
                    },
                })

        def add_field_mutations(target: str, key: str, value):
            # Missing
            tests.append({
                "name": f"missing_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REMOVE",
                    "original_value": value,
                    "new_value": None,
                    "strategy": "missing_param",
                },
            })

            # Null
            tests.append({
                "name": f"null_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": None,
                    "strategy": "null_param",
                },
            })

            # Empty + whitespace
            tests.append({
                "name": f"empty_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": "",
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"spaces_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": "   ",
                    "strategy": "invalid_param",
                },
            })

            # Type probes
            tests.append({
                "name": f"type_{target}_{key}_int",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": 0,
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"type_{target}_{key}_bool",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": True,
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"type_{target}_{key}_float",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": 0.1,
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"type_{target}_{key}_array",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": ["x"],
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"type_{target}_{key}_obj",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": {"_": "x"},
                    "strategy": "invalid_param",
                },
            })

            # Boundary-ish
            tests.append({
                "name": f"long_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": "A" * 4096,
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"unicode_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": "नमस्ते-测试-🚀",
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"specials_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": "!@#$%^&*()_+-=[]{}|;':,./<>?`~\\\"",
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"negative_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": -1,
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": f"huge_{target}_{key}",
                "mutation": {
                    "target": target,
                    "path": [key],
                    "operation": "REPLACE",
                    "original_value": value,
                    "new_value": 10**18,
                    "strategy": "invalid_param",
                },
            })

            # Security probes
            for idx, payload in enumerate(self.SECURITY_PAYLOADS, start=1):
                tests.append({
                    "name": f"security_{target}_{key}_{idx}",
                    "mutation": {
                        "target": target,
                        "path": [key],
                        "operation": "REPLACE",
                        "original_value": value,
                        "new_value": payload,
                        "strategy": "security",
                    },
                })

        # 3) Query mutations (GET)
        if params:
            for key, value in params.items():
                add_field_mutations("query", key, value)

        # 4) Whole-body mutations (POST/PUT/PATCH/etc)
        if method != "GET":
            tests.append({
                "name": "body_empty_object",
                "mutation": {
                    "target": "body_whole",
                    "path": [],
                    "operation": "REPLACE",
                    "original_value": body,
                    "new_value": {},
                    "strategy": "invalid_param",
                },
            })
            tests.append({
                "name": "body_null",
                "mutation": {
                    "target": "body_whole",
                    "path": [],
                    "operation": "REPLACE",
                    "original_value": body,
                    "new_value": None,
                    "strategy": "null_param",
                },
            })
            if isinstance(body, dict):
                tests.append({
                    "name": "body_extra_field",
                    "mutation": {
                        "target": "body_whole",
                        "path": [],
                        "operation": "REPLACE",
                        "original_value": body,
                        "new_value": {**body, "_unexpected": "x"},
                        "strategy": "invalid_param",
                    },
                })

        # 5) Body field mutations
        if method != "GET" and isinstance(body, dict) and body:
            for key, value in body.items():
                add_field_mutations("body", key, value)

        # 6) Optional OpenAPI schema-based request-body fuzzing (best-effort)
        try:
            from ai_testing_swarm.core.config import (
                AI_SWARM_OPENAPI_FUZZ,
                AI_SWARM_OPENAPI_FUZZ_MAX_DEPTH,
                AI_SWARM_OPENAPI_FUZZ_MAX_INVALID,
                AI_SWARM_OPENAPI_FUZZ_MAX_VALID,
            )

            if AI_SWARM_OPENAPI_FUZZ and isinstance(request.get("_openapi"), dict):
                ctx = request.get("_openapi") or {}
                spec = ctx.get("spec")
                path0 = ctx.get("path")
                method0 = ctx.get("method")

                if isinstance(spec, dict) and isinstance(path0, str) and isinstance(method0, str):
                    from ai_testing_swarm.core.openapi_fuzzer import generate_request_body_fuzz_cases

                    fuzz_cases = generate_request_body_fuzz_cases(
                        spec=spec,
                        path=path0,
                        method=method0,
                        max_valid=AI_SWARM_OPENAPI_FUZZ_MAX_VALID,
                        max_invalid=AI_SWARM_OPENAPI_FUZZ_MAX_INVALID,
                        max_depth=AI_SWARM_OPENAPI_FUZZ_MAX_DEPTH,
                    )

                    existing = {t["name"] for t in tests}
                    for fc in fuzz_cases:
                        name = fc.name
                        if name in existing:
                            continue
                        tests.append(
                            {
                                "name": name,
                                "mutation": {
                                    "target": "body_whole",
                                    "path": [],
                                    "operation": "REPLACE",
                                    "original_value": body,
                                    "new_value": fc.body,
                                    "strategy": "openapi_fuzz_valid" if fc.is_valid else "openapi_fuzz_invalid",
                                    "openapi": {"is_valid": fc.is_valid, "details": fc.details},
                                },
                            }
                        )
                        existing.add(name)
        except Exception:
            # Never fail planning due to fuzzing issues
            pass

        # 7) Optional OpenAI augmentation (best-effort)
        try:
            from ai_testing_swarm.core.config import (
                AI_SWARM_MAX_AI_TESTS,
                AI_SWARM_OPENAI_MODEL,
                AI_SWARM_USE_OPENAI,
            )

            if AI_SWARM_USE_OPENAI and AI_SWARM_MAX_AI_TESTS > 0:
                from ai_testing_swarm.core.openai_client import suggest_test_mutations

                ai_tests = suggest_test_mutations(
                    request=request,
                    max_tests=AI_SWARM_MAX_AI_TESTS,
                    model=AI_SWARM_OPENAI_MODEL,
                )

                existing = {t["name"] for t in tests}
                for t in ai_tests:
                    # avoid collisions
                    name = t.get("name")
                    if not name or name in existing:
                        continue
                    tests.append(t)
                    existing.add(name)

        except Exception:
            # Never fail planning due to OpenAI errors
            pass

        return tests
