from ai_testing_swarm.agents.test_planner_agent import TestPlannerAgent
from ai_testing_swarm.agents.execution_agent import ExecutionAgent
from ai_testing_swarm.agents.llm_reasoning_agent import LLMReasoningAgent
from ai_testing_swarm.agents.learning_agent import LearningAgent
from ai_testing_swarm.agents.release_gate_agent import ReleaseGateAgent
from ai_testing_swarm.reporting.report_writer import write_report
from ai_testing_swarm.core.safety import enforce_public_only
from ai_testing_swarm.core.risk import (
    RiskThresholds,
    compute_test_risk_score,
    compute_endpoint_risk_score,
    gate_from_score,
)

import logging

logger = logging.getLogger(__name__)
EXPECTED_FAILURES = {
    "success",
    "missing_param",
    "invalid_param",
    "security",
    "method_not_allowed",
    "not_found",
    "content_negotiation",
}

RISKY_FAILURES = {
    "unknown",
    "missing_param_accepted",
    "null_param_accepted",
    "invalid_param_accepted",
    "headers_accepted",
    "method_risk",
}

class SwarmOrchestrator:
    """
    Central brain of the AI Testing Swarm.
    """

    def __init__(self):
        self.planner = TestPlannerAgent()
        self.executor = ExecutionAgent()
        self.reasoner = LLMReasoningAgent()
        self.learner = LearningAgent()
        self.release_gate = ReleaseGateAgent()

    def run(
        self,
        request: dict,
        *,
        report_format: str = "json",
        gate_warn: int = 30,
        gate_block: int = 80,
        run_label: str | None = None,
        fail_on_regression: bool = False,
        return_report_path: bool = False,
    ):
        """Runs the full AI testing swarm.

        gate_warn/gate_block:
          Thresholds for PASS/WARN/BLOCK gate based on endpoint risk.
          (Kept optional for backward compatibility.)

        Returns:
          - default: (decision, results)
          - if return_report_path=True: (decision, results, report_path)
        """

        # Safety hook (currently no-op; kept for backward compatibility)
        enforce_public_only(request["url"])
        # --------------------------------------------------------
        # 1️⃣ PLAN TESTS
        # --------------------------------------------------------
        tests = self.planner.plan(request)
        results = []

        logger.info("🧠 Planned %d tests", len(tests))
        print(f"🧠 Planned {len(tests)} tests")

        # --------------------------------------------------------
        # 2️⃣ EXECUTE + CLASSIFY (parallel)
        # --------------------------------------------------------
        from concurrent.futures import ThreadPoolExecutor, as_completed
        from ai_testing_swarm.core.config import AI_SWARM_WORKERS, AI_SWARM_MAX_TESTS

        # Cap test count (avoid DoS on target / CI timeouts)
        tests = tests[:AI_SWARM_MAX_TESTS]

        def run_one(test: dict):
            test_name = test.get("name")
            execution_result = self.executor.execute(request, test)
            execution_result["name"] = test_name
            classification = self.reasoner.reason(execution_result)
            execution_result.update({
                "reason": classification.get("type"),
                "confidence": classification.get("confidence", 1.0),
                "failure_type": classification.get("type"),
                "status": (
                    "PASSED" if classification.get("type") in EXPECTED_FAILURES
                    else "RISK" if classification.get("type") in RISKY_FAILURES
                    else "FAILED"
                ),
            })

            # Batch1: numeric risk score per test (0..100)
            try:
                from ai_testing_swarm.core.config import AI_SWARM_SLA_MS

                execution_result["risk_score"] = compute_test_risk_score(
                    execution_result,
                    sla_ms=AI_SWARM_SLA_MS,
                )
            except Exception:
                # Keep backwards compatibility: don't fail the run if scoring breaks.
                execution_result["risk_score"] = 0

            # Optional learning step
            try:
                self.learner.learn(test_name, classification)
            except Exception as e:
                logger.warning("⚠️ Learning skipped for %s: %s", test_name, e)
            return execution_result

        results_by_name = {}
        with ThreadPoolExecutor(max_workers=AI_SWARM_WORKERS) as ex:
            futs = [ex.submit(run_one, t) for t in tests]
            for fut in as_completed(futs):
                r = fut.result()
                results_by_name[r["name"]] = r

        # Preserve deterministic order
        for t in tests:
            results.append(results_by_name[t["name"]])

        # --------------------------------------------------------
        # 3️⃣ RELEASE DECISION + RISK GATE
        # --------------------------------------------------------
        decision = self.release_gate.decide(results)

        thresholds = RiskThresholds(warn=int(gate_warn), block=int(gate_block))
        endpoint_risk_score = compute_endpoint_risk_score(results)
        gate_status = gate_from_score(endpoint_risk_score, thresholds)

        # --------------------------------------------------------
        # 4️⃣ WRITE REPORT
        # --------------------------------------------------------
        meta = {
            "decision": decision,
            "gate_status": gate_status,
            "gate_thresholds": {"warn": thresholds.warn, "block": thresholds.block},
            "endpoint_risk_score": endpoint_risk_score,
        }
        if run_label:
            meta["run_label"] = str(run_label)

        if fail_on_regression:
            meta["fail_on_regression"] = True

        # Optional AI summary for humans (best-effort)
        try:
            from ai_testing_swarm.core.config import AI_SWARM_AI_SUMMARY, AI_SWARM_OPENAI_MODEL, AI_SWARM_USE_OPENAI

            if AI_SWARM_USE_OPENAI and AI_SWARM_AI_SUMMARY:
                from ai_testing_swarm.core.openai_client import summarize_run

                meta["ai_summary"] = summarize_run(
                    request=request,
                    decision=decision,
                    results=results,
                    model=AI_SWARM_OPENAI_MODEL,
                )
        except Exception as e:
            meta["ai_summary_error"] = str(e)

        report_path = write_report(request, results, meta=meta, report_format=report_format)
        logger.info("📄 Swarm report written to: %s", report_path)
        print(f"📄 Swarm report written to: {report_path}")

        if return_report_path:
            return decision, results, report_path
        return decision, results
