from __future__ import annotations

import pytest
from pydantic import ValidationError

from cmvr_edge_ai.config import (
    MissingEnvironmentVariable,
    load_config_data,
    load_config_text,
)


def test_load_config_expands_environment_at_every_level() -> None:
    config = load_config_text(
        """
api_version: cmvr.edge.ai/v1
runtime:
  thread_workers: ${EDGE_THREADS:-2}
endpoints:
  robot:
    transport: GRPC
    target: env://CMVR_TARGET
  platform:
    transport: http
    base_url: ${PLATFORM_URL:-http://127.0.0.1:8080}
pipelines:
  detection:
    nodes:
      source:
        uses: core.sequence_source@1
        with:
          items: [env://SAMPLE_ITEM]
""",
        environ={
            "CMVR_TARGET": "192.168.0.10:50052",
            "EDGE_THREADS": "3",
            "SAMPLE_ITEM": "frame-1",
        },
    )

    assert config.runtime.thread_workers == 3
    assert config.endpoints["robot"].transport == "grpc"
    assert config.endpoints["robot"].target == "192.168.0.10:50052"
    assert config.endpoints["platform"].base_url == "http://127.0.0.1:8080"
    assert config.pipelines["detection"].nodes["source"].params["items"] == ["frame-1"]


def test_load_config_rejects_missing_required_environment_variable() -> None:
    with pytest.raises(MissingEnvironmentVariable, match="CMVR_TARGET"):
        load_config_text(
            """
api_version: cmvr.edge.ai/v1
endpoints:
  robot:
    transport: grpc
    target: ${CMVR_TARGET}
pipelines:
  smoke:
    nodes:
      source:
        uses: core.sequence_source@1
""",
            environ={},
        )


def test_config_rejects_edges_to_unknown_nodes() -> None:
    with pytest.raises(ValidationError, match="references unknown node"):
        load_config_data(
            {
                "api_version": "cmvr.edge.ai/v1",
                "pipelines": {
                    "invalid": {
                        "nodes": {
                            "source": {"uses": "test.source@1"},
                        },
                        "edges": [{"from": "source.output", "to": "missing.input"}],
                    }
                },
            }
        )


def test_config_rejects_cycles_before_runtime_construction() -> None:
    with pytest.raises(ValidationError, match="must be acyclic"):
        load_config_data(
            {
                "api_version": "cmvr.edge.ai/v1",
                "pipelines": {
                    "cyclic": {
                        "nodes": {
                            "first": {"uses": "test.operator@1"},
                            "second": {"uses": "test.operator@1"},
                        },
                        "edges": [
                            {"from": "first.output", "to": "second.input"},
                            {"from": "second.output", "to": "first.input"},
                        ],
                    }
                },
            }
        )


def test_config_models_forbid_unknown_fields() -> None:
    with pytest.raises(ValidationError, match="extra_forbidden"):
        load_config_data(
            {
                "api_version": "cmvr.edge.ai/v1",
                "unexpected": True,
                "pipelines": {
                    "smoke": {"nodes": {"source": {"uses": "test.source@1"}}}
                },
            }
        )


@pytest.mark.parametrize(
    ("endpoint", "message"),
    [
        ({"transport": "grpc"}, "gRPC endpoint requires target"),
        ({"transport": "http"}, "HTTP endpoint requires base_url"),
        (
            {
                "transport": "http",
                "base_url": "https://platform.example",
                "tls": False,
            },
            "HTTP endpoint must not set tls",
        ),
    ],
)
def test_builtin_endpoints_reject_incomplete_or_ambiguous_addresses(
    endpoint: dict[str, object], message: str
) -> None:
    with pytest.raises(ValidationError, match=message):
        load_config_data(
            {
                "api_version": "cmvr.edge.ai/v1",
                "endpoints": {"external": endpoint},
                "pipelines": {
                    "smoke": {"nodes": {"source": {"uses": "test.source@1"}}}
                },
            }
        )
