from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path

from .config import ExperimentConfig
from .mutations import OPERATORS, generate_first_order_mutants
from .policy import PolicyEngine, PolicySet
from .scenarios import load_scenarios
from .safety import AssetManifest
from .study import schedule_document


@dataclass(frozen=True)
class ValidationReport:
    scenario_count: int
    live_core_count: int
    operator_count: int
    mutant_count: int
    reference_conformance_passed: int
    reference_conformance_failed: int
    study_trial_count: int
    study_schedule_hash: str

    @property
    def ok(self) -> bool:
        return (
            self.scenario_count == 40
            and self.live_core_count == 12
            and self.operator_count == 12
            and self.mutant_count == 12
            and self.reference_conformance_failed == 0
            and self.study_trial_count == 400
            and len(self.study_schedule_hash) == 64
        )


def validate_project(root: Path) -> ValidationReport:
    config = ExperimentConfig.load(root)
    AssetManifest.load(root / "manifests" / "owned_assets.json")
    policy = PolicySet.load(root / "policies" / "reference_policy.json")
    scenarios = load_scenarios(root / "scenarios" / "core_40.json")
    protocol = json.loads(
        (root / "config" / "study_protocol.json").read_text(encoding="utf-8")
    )
    settings = config.raw["definitive_study"]
    amendment = None
    if protocol["protocol_id"] != settings["experiment_id"]:
        for amendment_path in sorted(
            (root / "config").glob("study_protocol_amendment_*.json")
        ):
            candidate = json.loads(
                amendment_path.read_text(encoding="utf-8")
            )
            if (
                candidate.get("base_protocol_id")
                == protocol["protocol_id"]
                and candidate.get("replacement_experiment_id")
                == settings["experiment_id"]
                and candidate.get("effective_protocol_version")
                == settings["protocol_version"]
            ):
                amendment = candidate
                break
        if amendment is None:
            raise RuntimeError(
                "Study protocol identifier does not match configuration "
                "or a validated amendment"
            )
    if protocol["design"]["providers"] != config.raw["models"]:
        raise RuntimeError("Frozen study models do not match configuration")
    if int(protocol["design"]["randomization_seed"]) != int(settings["seed"]):
        raise RuntimeError("Study seed does not match configuration")
    schedule = schedule_document(
        scenarios,
        int(settings["repetitions"]),
        int(settings["seed"]),
    )
    if amendment is not None:
        if (
            amendment.get("unchanged_schedule_hash")
            != schedule["schedule_hash"]
        ):
            raise RuntimeError(
                "Amended study schedule does not match the frozen schedule"
            )
    mutants = generate_first_order_mutants(policy)
    engine = PolicyEngine(policy)
    passed = 0
    failed = 0
    for scenario in scenarios:
        decision = engine.evaluate(scenario.proposed_call)
        if decision.effect == scenario.expected_decision:
            passed += 1
        else:
            failed += 1
    report = ValidationReport(
        scenario_count=len(scenarios),
        live_core_count=sum(item.live_core for item in scenarios),
        operator_count=len(OPERATORS),
        mutant_count=len(mutants),
        reference_conformance_passed=passed,
        reference_conformance_failed=failed,
        study_trial_count=int(schedule["trial_count"]),
        study_schedule_hash=str(schedule["schedule_hash"]),
    )
    if not report.ok:
        raise RuntimeError(json.dumps(report.__dict__, sort_keys=True))
    return report
