"""Combined capability proof: a project requiring Auth + Forms + a Feature Pack
(whose pack in turn depends on Auth) produces one deterministic merged runtime
requirements set and one inspectable plan per capability -- dependencies
deduplicated, correct ordering, no conflicts. This is the "composition proof"
complementing the single-capability tests in test_capability_adapter_runtime_
requirements.py and the fake-capability tests in test_capability_runtime_
requirements.py.
"""
from __future__ import annotations

from types import SimpleNamespace
from uuid import uuid4

import pytest
from src.domain.capabilities.adapters import (
    AuthV2Provisioner,
    FeaturePackProvisioner,
    FormsProvisioner,
)
from src.domain.capabilities.lifecycle import PlanAction
from src.domain.capabilities.runtime import merge_runtime_requirements


class _Result:
    def __init__(self, rows=None, scalars_list=None):
        self._rows = rows or []
        self._scalars_list = scalars_list or []

    def all(self):
        return self._rows

    def scalar_one_or_none(self):
        return None

    def scalars(self):
        return SimpleNamespace(all=lambda: self._scalars_list)


class _QueuedDB:
    """Pops queued results in call order; `.get()` always reports no reconciliation
    ledger row (DECLARED on first contact, matching a brand-new project)."""

    def __init__(self, *results: _Result):
        self._results = list(results)

    async def execute(self, _query):
        return self._results.pop(0)

    async def get(self, _model, _project_id):
        return None


def _forms_db() -> _QueuedDB:
    # One installed contact-capture pack, no discoverable project/App Block plan --
    # resolves the canonical "contact" form runtime (see
    # test_capability_adapter_runtime_requirements.py for the same fixture shape).
    pack_row = SimpleNamespace(pack_slug="contact-capture", status="installed", config={})
    # Installed packs, completed GenerationPlan lookup, then legacy Discovery
    # fallback lookup. Both composition reads are intentional: the plan-first
    # resolver must preserve compatibility with projects that have neither.
    return _QueuedDB(_Result(scalars_list=[pack_row]), _Result(), _Result())


def _feature_pack_db() -> _QueuedDB:
    # One installed pack that formally declares a capability dependency on auth,
    # plus a primitive-only dependency that the heuristic hint map also resolves to
    # "auth" -- both paths should collapse to the same single "auth" requirement
    # after merge, not double up.
    pack_row = SimpleNamespace(
        slug="staff-directory",
        composition={
            "schema_version": 2,
            "requires": [
                {"kind": "capability", "id": "auth", "min_version": "2"},
                {"kind": "primitive", "id": "auth.roles"},
            ],
        },
    )
    return _QueuedDB(_Result(rows=[("staff-directory",)]), _Result(scalars_list=[pack_row]))


@pytest.mark.asyncio
async def test_combined_project_merges_auth_forms_and_feature_pack_requirements():
    project_id = uuid4()

    auth = AuthV2Provisioner(db=None, cloud_base_url="https://cloud.example", cloud_token="tok")
    forms = FormsProvisioner(_forms_db())
    feature_packs = FeaturePackProvisioner(
        _feature_pack_db(), capability_providers={"auth": auth, "forms": forms}
    )

    auth_req = await auth.runtime_requirements(project_id)
    forms_req = await forms.runtime_requirements(project_id)
    feature_pack_req = await feature_packs.runtime_requirements(project_id)

    result = merge_runtime_requirements([auth_req, forms_req, feature_pack_req])

    assert result.ok, result.conflicts
    merged = result.merged
    # Auth's route is declared twice (directly, and via the Feature Pack's
    # aggregation) but appears exactly once after merge.
    assert sorted(r.path_prefix for r in merged.routes) == ["/api/auth", "/api/forms"]
    assert "p2hs-auth" in merged.platform_services
    assert "p2hs-forms" in merged.platform_services


@pytest.mark.asyncio
async def test_combined_project_capability_dependency_resolves_without_conflict():
    """The Feature Pack's formally-declared auth>=2 dependency resolves cleanly
    against a project that also directly declares auth at version 2."""
    feature_packs = FeaturePackProvisioner(_feature_pack_db(), capability_providers={})

    resolution = await feature_packs._capability_dependency_resolution(uuid4())

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


@pytest.mark.asyncio
async def test_combined_project_plan_is_one_deterministic_entry_per_capability():
    """Each capability's plan() is independently inspectable before any apply --
    the 'one deterministic plan' composition proof, without requiring a single
    combined-plan abstraction on top."""
    project_id = uuid4()
    forms = FormsProvisioner(_forms_db())
    feature_packs = FeaturePackProvisioner(_feature_pack_db(), capability_providers={})

    forms_plan = await forms.plan(project_id)
    feature_pack_plan = await feature_packs.plan(project_id)

    assert forms_plan.action == PlanAction.NOOP
    assert feature_pack_plan.action == PlanAction.PROVISION
    assert feature_pack_plan.changes == ("reconcile_feature_packs",)
