"""Integration tests for the core bridge emitter."""

from __future__ import annotations

import json
import re
from pathlib import Path

import pytest
from abi_control_core.sdk.validate import validate_json

from dpe.core_bridge import CoreBridgeValidationError, emit_core_artifacts
from dpe.models.config import DPEConfig
from dpe.models.manifest import RunManifest


class TestFullEmission:
    def test_writes_all_three_files(
        self,
        tmp_path: Path,
        completed_manifest: RunManifest,
        default_config: DPEConfig,
        sample_stage_outputs: dict,
    ) -> None:
        result = emit_core_artifacts(
            manifest=completed_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs=sample_stage_outputs,
        )
        assert result.plan_graph_path.exists()
        assert result.eval_suite_path.exists()
        assert result.core_manifest_path.exists()

    def test_plan_graph_validates(
        self,
        tmp_path: Path,
        completed_manifest: RunManifest,
        default_config: DPEConfig,
        sample_stage_outputs: dict,
    ) -> None:
        emit_core_artifacts(
            manifest=completed_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs=sample_stage_outputs,
        )
        data = json.loads((tmp_path / "plan_graph.json").read_text())
        validate_json(data, "plan_graph.schema.json")

    def test_eval_suite_validates(
        self,
        tmp_path: Path,
        completed_manifest: RunManifest,
        default_config: DPEConfig,
        sample_stage_outputs: dict,
    ) -> None:
        emit_core_artifacts(
            manifest=completed_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs=sample_stage_outputs,
        )
        data = json.loads((tmp_path / "eval_suite.json").read_text())
        validate_json(data, "eval_suite.schema.json")

    def test_run_manifest_core_validates(
        self,
        tmp_path: Path,
        completed_manifest: RunManifest,
        default_config: DPEConfig,
        sample_stage_outputs: dict,
    ) -> None:
        emit_core_artifacts(
            manifest=completed_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs=sample_stage_outputs,
        )
        data = json.loads((tmp_path / "run_manifest.core.json").read_text())
        validate_json(data, "run_manifest.schema.json")

    def test_manifest_hash_present_in_written_file(
        self,
        tmp_path: Path,
        completed_manifest: RunManifest,
        default_config: DPEConfig,
        sample_stage_outputs: dict,
    ) -> None:
        result = emit_core_artifacts(
            manifest=completed_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs=sample_stage_outputs,
        )
        data = json.loads((tmp_path / "run_manifest.core.json").read_text())
        assert "manifest_hash_jcs_sha256" in data
        assert re.match(r"^jcs_sha256:[0-9a-f]{64}$", data["manifest_hash_jcs_sha256"])
        assert result.manifest_hash in data["manifest_hash_jcs_sha256"]

    def test_manifest_hash_is_deterministic(
        self,
        tmp_path: Path,
        completed_manifest: RunManifest,
        default_config: DPEConfig,
        sample_stage_outputs: dict,
    ) -> None:
        r1 = emit_core_artifacts(
            manifest=completed_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs=sample_stage_outputs,
        )
        r2 = emit_core_artifacts(
            manifest=completed_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs=sample_stage_outputs,
        )
        assert r1.manifest_hash == r2.manifest_hash


class TestBudgetExceededEmission:
    def test_validates_budget_exceeded_run(
        self,
        tmp_path: Path,
        budget_exceeded_manifest: RunManifest,
        default_config: DPEConfig,
    ) -> None:
        result = emit_core_artifacts(
            manifest=budget_exceeded_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs={},
        )
        # All three files should exist and be valid
        for path in [
            result.plan_graph_path,
            result.eval_suite_path,
            result.core_manifest_path,
        ]:
            assert path.exists()

        data = json.loads(result.core_manifest_path.read_text())
        assert data["verdict"] == "warn"


class TestErrorRunEmission:
    def test_validates_error_run(
        self,
        tmp_path: Path,
        error_manifest: RunManifest,
        default_config: DPEConfig,
    ) -> None:
        result = emit_core_artifacts(
            manifest=error_manifest,
            run_dir=tmp_path,
            config=default_config,
            stage_outputs={},
        )
        data = json.loads(result.core_manifest_path.read_text())
        assert data["verdict"] == "fail"


class TestValidationFailure:
    def test_writes_validation_errors_on_failure(
        self,
        tmp_path: Path,
        completed_manifest: RunManifest,
        default_config: DPEConfig,
        sample_stage_outputs: dict,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        # Corrupt the plan graph builder to produce invalid output
        from dpe.core_bridge import _emitter

        original = _emitter.build_plan_graph

        def broken_builder(manifest: RunManifest) -> dict:
            pg = original(manifest)
            del pg["schema_version"]  # Remove required field
            return pg

        monkeypatch.setattr(_emitter, "build_plan_graph", broken_builder)

        with pytest.raises(CoreBridgeValidationError) as exc_info:
            emit_core_artifacts(
                manifest=completed_manifest,
                run_dir=tmp_path,
                config=default_config,
                stage_outputs=sample_stage_outputs,
            )

        # validation_errors.json should exist
        errors_path = tmp_path / "validation_errors.json"
        assert errors_path.exists()
        errors_data = json.loads(errors_path.read_text())
        assert len(errors_data["errors"]) > 0
        assert errors_data["errors"][0]["file"] == "plan_graph.json"

        # The exception should carry the errors
        assert len(exc_info.value.errors) > 0
