import argparse
import json
from ai_testing_swarm.orchestrator import SwarmOrchestrator
from ai_testing_swarm.core.curl_parser import parse_curl


def normalize_request(payload: dict) -> dict:
    """Normalize input into execution-ready request.

    Supported formats:
    1) { "curl": "curl ..." }
    2) { "method": "...", "url": "...", "headers": {...}, "body": {...} }
    3) OpenAPI-driven (optional):
       {
         "openapi": "./openapi.json" | "https://.../openapi.json",
         "path": "/pets",
         "method": "GET",
         "headers": {...},
         "path_params": {...},
         "query_params": {...},
         "body": {...}
       }
    """

    # Case 1: raw curl input
    if "curl" in payload:
        return parse_curl(payload["curl"])

    # Case 3: OpenAPI
    if "openapi" in payload:
        from ai_testing_swarm.core.openapi_loader import load_openapi, build_request_from_openapi

        spec = load_openapi(payload["openapi"])
        req = build_request_from_openapi(
            spec,
            path=payload["path"],
            method=payload["method"],
            headers=payload.get("headers") or {},
            path_params=payload.get("path_params") or {},
            query_params=payload.get("query_params") or {},
            body=payload.get("body"),
        )
        # Attach OpenAPI context for optional response validation/reporting.
        req["_openapi"] = {
            "source": payload["openapi"],
            "path": payload["path"],
            "method": str(payload["method"]).upper(),
            "spec": spec,
        }
        return req

    # Case 2: already normalized
    required_keys = {"method", "url"}
    if required_keys.issubset(payload.keys()):
        return payload

    raise ValueError(
        "Invalid input format.\n"
        "Expected either:\n"
        "1) { \"curl\": \"curl ...\" }\n"
        "2) { \"method\": \"POST\", \"url\": \"...\", \"headers\": {}, \"body\": {} }\n"
        "3) { \"openapi\": \"...\", \"path\": \"/x\", \"method\": \"GET\" }"
    )


def main():
    parser = argparse.ArgumentParser(
        description="AI Testing Swarm CLI (advanced API mutation testing)",
        epilog=(
            "Environment toggles (optional):\n"
            "  AI_SWARM_WORKERS=5            Parallel workers\n"
            "  AI_SWARM_MAX_TESTS=80         Max tests per run\n"
            "  AI_SWARM_RETRY_COUNT=1        Retries on transient failures\n"
            "  AI_SWARM_RETRY_BACKOFF_MS=250 Backoff base\n"
            "  AI_SWARM_RPS=0                Throttle (requests/sec), 0=off\n"
            "  AI_SWARM_SLA_MS=2000          Slow-test threshold\n"
            "  AI_SWARM_USE_OPENAI=1         Enable OpenAI augmentation\n"
            "  AI_SWARM_MAX_AI_TESTS=20      Extra AI-generated tests\n"
            "  AI_SWARM_AI_SUMMARY=1         Add AI summary to report\n"
            "  AI_SWARM_OPENAI_MODEL=...     Model name\n"
            "  OPENAI_API_KEY=...            Required if OpenAI enabled\n"
            "  AI_SWARM_OPENAPI_BASE_URL=... Override OpenAPI servers[]\n"
            "  AI_SWARM_REDACT_ENABLED=0     Enable report redaction (1=on, default off)\n"
            "  AI_SWARM_REDACT_KEYS=...      Comma-separated extra sensitive keys\n"
            "  AI_SWARM_HTML_REPORT=1        Generate standalone HTML reports\n"
            "  AI_SWARM_REPORT_HISTORY_LIMIT=120 Retain this many runs per endpoint\n"
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    from ai_testing_swarm import __version__

    parser.add_argument("--version", action="version", version=f"ai-testing-swarm {__version__}")

    parser.add_argument(
        "--input",
        required=True,
        help="Path to request.json"
    )

    parser.add_argument(
        "--public-only",
        action="store_true",
        help="Safety: allow only public test hosts (httpbin/postman-echo/reqres) for this run",
    )

    parser.add_argument(
        "--report-format",
        default="json",
        choices=["json", "md", "html"],
        help="Report format to write (default: json)",
    )

    # OpenAPI schema-based request-body fuzzing
    parser.add_argument(
        "--openapi-fuzz",
        dest="openapi_fuzz",
        action="store_true",
        default=True,
        help="Enable OpenAPI schema-based request-body fuzzing when OpenAPI context is present (default: on)",
    )
    parser.add_argument(
        "--no-openapi-fuzz",
        dest="openapi_fuzz",
        action="store_false",
        help="Disable OpenAPI schema-based request-body fuzzing",
    )
    parser.add_argument(
        "--openapi-fuzz-max-valid",
        type=int,
        default=8,
        help="Max schema-valid OpenAPI fuzz cases to add (default: 8)",
    )
    parser.add_argument(
        "--openapi-fuzz-max-invalid",
        type=int,
        default=8,
        help="Max schema-invalid OpenAPI fuzz cases to add (default: 8)",
    )
    parser.add_argument(
        "--openapi-fuzz-max-depth",
        type=int,
        default=6,
        help="Max recursion depth for OpenAPI schema instance generation (default: 6)",
    )

    parser.add_argument(
        "--fail-on-regression",
        action="store_true",
        help="Exit non-zero if the run regresses vs the previous JSON report for this endpoint",
    )

    parser.add_argument(
        "--auth-matrix",
        default="",
        help=(
            "Optional path to auth_matrix.yaml/json to run the same endpoint under multiple auth headers. "
            "Each case is reported separately via a run label suffix."
        ),
    )

    # Batch1: risk gate thresholds (backward compatible defaults)
    parser.add_argument(
        "--gate-warn",
        type=int,
        default=30,
        help="Gate WARN threshold for endpoint risk score (default: 30)",
    )
    parser.add_argument(
        "--gate-block",
        type=int,
        default=80,
        help="Gate BLOCK threshold for endpoint risk score (default: 80)",
    )

    args = parser.parse_args()

    # ------------------------------------------------------------
    # Load input JSON
    # ------------------------------------------------------------
    with open(args.input) as f:
        payload = json.load(f)

    # ------------------------------------------------------------
    # 🔴 NORMALIZE INPUT (THIS FIXES YOUR ERROR)
    # ------------------------------------------------------------
    request = normalize_request(payload)

    # ------------------------------------------------------------
    # Run swarm
    # ------------------------------------------------------------
    import os

    if args.public_only:
        os.environ["AI_SWARM_PUBLIC_ONLY"] = "1"

    # CLI flags -> env toggles used by core.config
    os.environ["AI_SWARM_OPENAPI_FUZZ"] = "1" if args.openapi_fuzz else "0"
    os.environ["AI_SWARM_OPENAPI_FUZZ_MAX_VALID"] = str(int(args.openapi_fuzz_max_valid))
    os.environ["AI_SWARM_OPENAPI_FUZZ_MAX_INVALID"] = str(int(args.openapi_fuzz_max_invalid))
    os.environ["AI_SWARM_OPENAPI_FUZZ_MAX_DEPTH"] = str(int(args.openapi_fuzz_max_depth))

    orch = SwarmOrchestrator()

    def _print_console(decision, results, *, label: str = ""):
        if label:
            print(f"\n=== AUTH CASE: {label} ===")
        print("\n=== RELEASE DECISION ===")
        print(decision)
        print("\n=== TEST RESULTS ===")
        for r in results:
            response = r.get("response", {})
            status_code = response.get("status_code")
            print(f"{r.get('name'):25} {str(status_code):5} {r.get('reason')}")

    def _maybe_fail_on_regression(report_path: str):
        try:
            with open(report_path, "r", encoding="utf-8") as f:
                report = json.load(f)
        except Exception:
            return

        trend = report.get("trend") or {}
        regression_count = trend.get("regression_count")
        try:
            reg_i = int(regression_count or 0)
        except Exception:
            reg_i = 0

        if reg_i > 0:
            print(f"\n❌ Regression gate failed: regressions={reg_i} (see trend in report)")
            raise SystemExit(2)

    if args.auth_matrix:
        from ai_testing_swarm.core.auth_matrix import load_auth_matrix, merge_auth_headers

        cases = load_auth_matrix(args.auth_matrix)
        for c in cases:
            req2 = merge_auth_headers(request, c)
            report_format = args.report_format
            if args.fail_on_regression and report_format != "json":
                # regression gate needs a machine-readable report
                report_format = "json"

            decision, results, report_path = orch.run(
                req2,
                report_format=report_format,
                gate_warn=args.gate_warn,
                gate_block=args.gate_block,
                run_label=f"auth-{c.name}",
                fail_on_regression=args.fail_on_regression,
                return_report_path=True,
            )
            _print_console(decision, results, label=c.name)

            if args.fail_on_regression:
                _maybe_fail_on_regression(report_path)
    else:
        report_format = args.report_format
        if args.fail_on_regression and report_format != "json":
            report_format = "json"

        decision, results, report_path = orch.run(
            request,
            report_format=report_format,
            gate_warn=args.gate_warn,
            gate_block=args.gate_block,
            fail_on_regression=args.fail_on_regression,
            return_report_path=True,
        )
        _print_console(decision, results)

        if args.fail_on_regression:
            _maybe_fail_on_regression(report_path)


if __name__ == "__main__":
    main()
