"""Capability-level dependency resolution: transitive resolution, dedup, cycle
detection, and version-conflict detection for ``CapabilityDependency`` /
``resolve_dependencies``. Scoped to the version semantics actually supported --
minimum_version (>=) and exact_version (==), both plain ints."""
from __future__ import annotations

from src.domain.capabilities.dependencies import CapabilityDependency, resolve_dependencies


def test_transitive_dependencies_resolve_in_dependency_before_dependent_order():
    # A requires B, B requires C => C, B, A
    resolution = resolve_dependencies(
        roots={"a": 1},
        dependencies={
            "a": [CapabilityDependency(key="b")],
            "b": [CapabilityDependency(key="c")],
        },
    )

    assert resolution.ok
    assert [r.key for r in resolution.order] == ["c", "b", "a"]


def test_shared_dependency_is_deduplicated_once():
    # A requires C, B requires C => C appears once
    resolution = resolve_dependencies(
        roots={"a": 1, "b": 1},
        dependencies={
            "a": [CapabilityDependency(key="c")],
            "b": [CapabilityDependency(key="c")],
        },
    )

    assert resolution.ok
    keys = [r.key for r in resolution.order]
    assert keys.count("c") == 1
    c = next(r for r in resolution.order if r.key == "c")
    assert set(c.required_by) == {"a", "b"}


def test_cycle_is_reported_as_a_structured_failure_not_an_exception():
    # A -> B -> A
    resolution = resolve_dependencies(
        roots={"a": 1},
        dependencies={
            "a": [CapabilityDependency(key="b")],
            "b": [CapabilityDependency(key="a")],
        },
    )

    assert resolution.ok is False
    assert len(resolution.cycles) == 1
    assert resolution.cycles[0].cycle == ("a", "b", "a")


def test_version_conflict_between_minimum_and_exact_is_detected():
    # A requires auth >= 2, B requires auth == 1 => conflict
    resolution = resolve_dependencies(
        roots={"a": 1, "b": 1},
        dependencies={
            "a": [CapabilityDependency(key="auth", minimum_version=2)],
            "b": [CapabilityDependency(key="auth", exact_version=1)],
        },
    )

    assert resolution.ok is False
    assert len(resolution.version_conflicts) == 1
    conflict = resolution.version_conflicts[0]
    assert conflict.key == "auth"
    assert set(conflict.requirers) == {"a", "b"}


def test_compatible_minimum_versions_resolve_to_the_highest_floor():
    resolution = resolve_dependencies(
        roots={"a": 1, "b": 1},
        dependencies={
            "a": [CapabilityDependency(key="auth", minimum_version=1)],
            "b": [CapabilityDependency(key="auth", minimum_version=2)],
        },
    )

    assert resolution.ok
    auth = next(r for r in resolution.order if r.key == "auth")
    assert auth.version == 2


def test_optional_dependency_absence_is_not_a_resolution_failure():
    resolution = resolve_dependencies(
        roots={"a": 1},
        dependencies={
            "a": [CapabilityDependency(key="nice_to_have", required=False)],
        },
    )

    assert resolution.ok
    optional = next(r for r in resolution.order if r.key == "nice_to_have")
    assert optional.required is False


def test_leaf_capability_absent_from_dependencies_resolves_with_no_deps():
    resolution = resolve_dependencies(roots={"forms": 1}, dependencies={})

    assert resolution.ok
    assert [r.key for r in resolution.order] == ["forms"]


def test_dependency_cannot_declare_both_minimum_and_exact_version():
    import pytest

    with pytest.raises(ValueError):
        CapabilityDependency(key="auth", minimum_version=1, exact_version=2)


def test_resolution_order_is_deterministic_across_repeated_calls():
    roots = {"a": 1, "b": 1}
    dependencies = {
        "a": [CapabilityDependency(key="c"), CapabilityDependency(key="d")],
        "b": [CapabilityDependency(key="d")],
    }

    first = resolve_dependencies(roots=roots, dependencies=dependencies)
    second = resolve_dependencies(roots=roots, dependencies=dependencies)

    assert [r.key for r in first.order] == [r.key for r in second.order]
