"""Behave steps to cover reactive config parser paths."""

from __future__ import annotations

import os
import tempfile
from pathlib import Path
from typing import Any

import yaml
from behave import given, then, when

from cleveragents.core.exceptions import ConfigurationError
from cleveragents.reactive.config_parser import ReactiveConfigParser
from cleveragents.reactive.route import RouteType
from cleveragents.reactive.stream_router import StreamType


@given("a temporary directory for reactive config parsing")
def step_temp_directory(context: Any) -> None:
    context.temp_dir = Path(tempfile.mkdtemp())
    context.parser = ReactiveConfigParser()
    context.config_files: list[Path] = []
    context.parsed_config = None
    context.parse_error: Exception | None = None


@given("reactive config files covering merges, routes, and pipelines")
def step_reactive_config_files(context: Any) -> None:
    stream_route = {
        "type": "stream",
        "stream_type": "hot",
        "operators": [{"type": "map"}],
        "subscriptions": ["input"],
        "publications": ["output"],
        "agents": ["alpha"],
        "initial_value": 5,
        "buffer_size": 2,
        "template_config": {"tmpl": True},
        "bridge": {
            "upgrade_conditions": {"ready": True},
            "downgrade_conditions": {},
            "state_extractor": "extract",
            "state_flattener": "flatten",
            "preserve_subscriptions": False,
            "preserve_checkpointing": False,
        },
        "metadata": {"kind": "stream"},
    }

    graph_route = {
        "type": "graph",
        "nodes": {"n1": {"agent": "alpha"}},
        "edges": [{"from": "n1", "to": "n1"}],
        "entry_point": "n1",
        "checkpointing": True,
        "checkpoint_dir": str(context.temp_dir / "ckpts"),
        "enable_time_travel": True,
        "parallel_execution": False,
        "state_class": "State",
        "metadata": {"kind": "graph"},
    }

    primary_config = {
        "agents": {"alpha": {"type": "llm", "config": {"a": 1}}},
        "routes": [stream_route, graph_route],
        "merges": [{"sources": ["input"], "target": "route_0"}],
        "splits": [{"source": "route_0", "targets": ["route_1"]}],
    }

    secondary_config = {
        "agents": {"alpha": {"config": {"b": 2}}},
        "merges": [{"sources": ["input2"], "target": "route_0"}],
        "templates": {"t1": {"value": 1}},
        "instances": {"inst": {"config": "val"}},
        "global_context": {"mode": "test"},
        "template_engine": "CUSTOM",
        "prompts": {"p1": "hi"},
        "pipelines": {
            "pipe1": {
                "stages": [
                    {"type": "stream", "name": "route_0"},
                    {"type": "graph", "config": {"name": "route_1"}},
                ],
                "metadata": {"note": "x"},
            }
        },
    }

    primary_file = context.temp_dir / "primary.yaml"
    secondary_file = context.temp_dir / "secondary.yaml"
    primary_file.write_text(yaml.safe_dump(primary_config))
    secondary_file.write_text(yaml.safe_dump(secondary_config))
    context.config_files = [primary_file, secondary_file]


@when("I parse the reactive config files")
def step_parse_reactive_config_files(context: Any) -> None:
    try:
        context.parsed_config = context.parser.parse_files(context.config_files)
        context.parse_error = None
    except Exception as exc:  # pragma: no cover - defensive
        context.parsed_config = None
        context.parse_error = exc


@then("the merged reactive config should preserve agents and replacements")
def step_verify_merges_and_agents(context: Any) -> None:
    assert context.parse_error is None, f"Unexpected error: {context.parse_error}"
    rc = context.parsed_config
    assert rc is not None
    assert rc.agents["alpha"].type == "llm"
    assert rc.agents["alpha"].config["a"] == 1
    assert rc.agents["alpha"].config["b"] == 2
    assert len(rc.merges) == 2


@then("stream and graph routes should be built with defaults and bridges")
def step_verify_routes(context: Any) -> None:
    rc = context.parsed_config
    assert rc is not None

    stream_route = rc.routes["route_0"]
    assert stream_route.type == RouteType.STREAM
    assert stream_route.stream_type == StreamType.HOT
    assert stream_route.buffer_size == 2
    assert stream_route.template_config == {"tmpl": True}
    assert stream_route.bridge is not None
    assert stream_route.bridge.preserve_subscriptions is False
    assert stream_route.bridge.preserve_checkpointing is False

    graph_route = rc.routes["route_1"]
    assert graph_route.type == RouteType.GRAPH
    assert graph_route.checkpointing is True
    assert graph_route.enable_time_travel is True
    assert graph_route.parallel_execution is False
    assert graph_route.state_class == "State"


@then("reactive config should include merges splits pipelines and templates")
def step_verify_other_sections(context: Any) -> None:
    rc = context.parsed_config
    assert rc is not None

    assert len(rc.splits) == 1
    assert "pipe1" in rc.pipelines
    assert rc.pipelines["pipe1"].metadata["note"] == "x"
    assert "t1" in rc.templates
    assert "inst" in rc.instances
    assert rc.global_context["mode"] == "test"
    assert rc.template_engine == "CUSTOM"
    assert rc.prompts["p1"] == "hi"


@given("a reactive config file with environment placeholders")
def step_env_config_file(context: Any) -> None:
    config = {
        "agents": {
            "env_agent": {
                "type": "llm",
                "config": {
                    "api_key": "${ENV_API_KEY}",
                    "with_default": "${MISSING_KEY:defaulted}",
                },
            }
        },
        "routes": {"main": {"type": "stream", "agents": ["env_agent"]}},
    }
    config_file = context.temp_dir / "env.yaml"
    config_file.write_text(yaml.safe_dump(config))
    context.config_files = [config_file]


@given("environment variables are set for interpolation")
def step_set_env_vars(_: Any) -> None:
    os.environ["ENV_API_KEY"] = "secret-key"
    os.environ["OTHER_VAR"] = "unused"


@then("environment placeholders should resolve using values and defaults")
def step_verify_env_interpolation(context: Any) -> None:
    assert context.parse_error is None, f"Unexpected error: {context.parse_error}"
    rc = context.parsed_config
    assert rc is not None

    agent_cfg = rc.agents["env_agent"].config
    assert agent_cfg["api_key"] == "secret-key"
    assert agent_cfg["with_default"] == "defaulted"


@given("a reactive config file with a required environment placeholder")
def step_env_required_config(context: Any) -> None:
    config = {
        "agents": {"needs_env": {"type": "llm", "config": {"token": "${MUST_HAVE}"}}},
        "routes": {"main": {"type": "stream", "agents": ["needs_env"]}},
    }
    file_path = context.temp_dir / "missing_env.yaml"
    file_path.write_text(yaml.safe_dump(config))
    context.config_files = [file_path]
    if "MUST_HAVE" in os.environ:
        del os.environ["MUST_HAVE"]


@when("I parse the reactive config files expecting failure")
def step_parse_expect_failure(context: Any) -> None:
    try:
        context.parsed_config = context.parser.parse_files(context.config_files)
        context.parse_error = None
    except Exception as exc:
        context.parsed_config = None
        context.parse_error = exc


@then("a configuration error should be raised for the missing variable")
def step_verify_missing_env_error(context: Any) -> None:
    assert context.parse_error is not None, "Expected a configuration error"
    assert isinstance(context.parse_error, ConfigurationError)
