"""§22.12 S6 — per-collection model universe.

A collection's `.collection.yaml` may carry an `enabled_models` list that
NARROWS its project's universe, which in turn narrows the deployment
ENABLED_MODELS. The resolution chain (extending §6.6/§6.7) is:

    funder ∩ per-entry models ∩ collection universe ∩ project universe
                                            (operator providers = the ceiling)

These tests prove (1) the manifest parser reads `enabled_models`, (2) the
mirror stores it on the collection row, (3) the resolver narrows the base
universe by project then collection, with absent = inherit and [] = opt-out,
and (4) the collection API surfaces the collection's own narrowing.
"""
from __future__ import annotations

import json

from fastapi.testclient import TestClient

from app import db, models_resolver, registry
from test_propose_vertical import (  # noqa: F401
    app_with_fake_gitea, provision_user_row, sign_in_as, tmp_env,
)
from test_rfc_view_vertical import FakeProvider, seed_active_rfc  # noqa: F401


def _install_two_providers(app) -> None:
    app.state.providers.clear()
    app.state.providers["claude"] = FakeProvider("TITLE: A\nDESCRIPTION: B")
    app.state.providers["gemini"] = FakeProvider("TITLE: G\nDESCRIPTION: H")


def _set_project_models(project_id: str, models) -> None:
    cfg = {} if models is None else {"enabled_models": models}
    db.conn().execute(
        "UPDATE projects SET config_json = ? WHERE id = ?",
        (json.dumps(cfg), project_id),
    )


def _set_collection_models(collection_id: str, models) -> None:
    cfg = None if models is None else json.dumps({"enabled_models": models})
    db.conn().execute(
        "UPDATE collections SET config_json = ? WHERE id = ?",
        (cfg, collection_id),
    )


# ── manifest parsing ───────────────────────────────────────────────────────


def test_manifest_parses_enabled_models_into_config():
    ce = registry.parse_collection_manifest(
        "type: bdd\nname: Features\nenabled_models: [claude, gemini]\n"
    )
    assert ce.config.get("enabled_models") == ["claude", "gemini"]


def test_manifest_without_enabled_models_has_no_key():
    ce = registry.parse_collection_manifest("type: document\nname: Model\n")
    assert "enabled_models" not in ce.config


def test_manifest_rejects_non_list_enabled_models():
    import pytest
    with pytest.raises(registry.RegistryError):
        registry.parse_collection_manifest("type: bdd\nenabled_models: claude\n")


# ── resolver narrowing (the §22.12 chain) ──────────────────────────────────


def test_resolver_inherits_operator_universe_when_no_scope_narrowing(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app):
        seed_active_rfc(fake, slug="ohm", title="OHM", body="x")
        _install_two_providers(app)
        # No project/collection narrowing → full operator universe.
        resolved = models_resolver.resolve_models_for_rfc("ohm", app.state.providers)
        assert resolved == ["claude", "gemini"]


def test_resolver_narrows_by_project_universe(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app):
        seed_active_rfc(fake, slug="ohm", title="OHM", body="x")
        _install_two_providers(app)
        _set_project_models("default", ["gemini"])
        resolved = models_resolver.resolve_models_for_rfc("ohm", app.state.providers)
        assert resolved == ["gemini"]


def test_resolver_collection_narrows_within_project(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app):
        seed_active_rfc(fake, slug="ohm", title="OHM", body="x")
        _install_two_providers(app)
        # Project allows both; the collection narrows to claude only.
        _set_project_models("default", ["claude", "gemini"])
        _set_collection_models("default", ["claude"])
        resolved = models_resolver.resolve_models_for_rfc("ohm", app.state.providers)
        assert resolved == ["claude"]


def test_resolver_collection_empty_list_opts_out(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app):
        seed_active_rfc(fake, slug="ohm", title="OHM", body="x")
        _install_two_providers(app)
        _set_collection_models("default", [])  # opt this collection out of AI
        resolved = models_resolver.resolve_models_for_rfc("ohm", app.state.providers)
        assert resolved == []


def test_resolver_collection_cannot_widen_project(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app):
        seed_active_rfc(fake, slug="ohm", title="OHM", body="x")
        _install_two_providers(app)
        # Project restricts to gemini; the collection naming claude+gemini
        # cannot re-add claude (narrowing only).
        _set_project_models("default", ["gemini"])
        _set_collection_models("default", ["claude", "gemini"])
        resolved = models_resolver.resolve_models_for_rfc("ohm", app.state.providers)
        assert resolved == ["gemini"]


# ── API surfacing ──────────────────────────────────────────────────────────


def test_collection_api_surfaces_enabled_models(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        provision_user_row(user_id=1, login="ben", role="owner")
        sign_in_as(client, user_id=1, gitea_login="ben", display_name="Ben",
                   role="owner", email="ben@test")
        _set_collection_models("default", ["claude"])
        r = client.get("/api/projects/default/collections/default")
        assert r.status_code == 200, r.text
        assert r.json().get("enabled_models") == ["claude"]
