"""§22 S2 — the registry mirror reads `.collection.yaml` manifests inside each
project's content repo and upserts a named collection per manifest. The default
collection still flows from projects.yaml (test_registry.py)."""
from __future__ import annotations

import asyncio
import base64
import tempfile
from pathlib import Path

import pytest

from app import db, registry
from app.config import Config


def _db() -> Config:
    cfg = Config(
        gitea_url="x", gitea_bot_user="x", gitea_bot_token="x", gitea_org="wiggleverse",
        registry_repo="registry", oauth_client_id="x",
        oauth_client_secret="x", app_url="x", secret_key="x",
        database_path=Path(tempfile.mkdtemp(prefix="colreg-")) / "t.db",
        owner_gitea_login="x", webhook_secret="x",
    )
    db.run_migrations(cfg)
    if db._CONN is not None:
        db._CONN.close()
        db._CONN = None
    db.init(cfg)
    return cfg


# --- pure parser --------------------------------------------------------------


def test_parse_collection_manifest_minimal():
    doc = registry.parse_collection_manifest("type: bdd\n")
    assert doc.type == "bdd"
    # §22.4b: bdd defaults to 'active'; visibility inherits (None == inherit).
    assert doc.initial_state == "active"
    assert doc.visibility is None
    assert doc.name is None


def test_parse_collection_manifest_full():
    doc = registry.parse_collection_manifest(
        "type: document\nvisibility: public\ninitial_state: active\nname: Model\n"
    )
    assert (doc.type, doc.visibility, doc.initial_state, doc.name) == (
        "document", "public", "active", "Model",
    )


def test_parse_collection_manifest_rejects_bad_type():
    with pytest.raises(registry.RegistryError):
        registry.parse_collection_manifest("type: nonsense\n")


def test_parse_collection_manifest_rejects_bad_visibility():
    with pytest.raises(registry.RegistryError):
        registry.parse_collection_manifest("type: bdd\nvisibility: nope\n")


# ---- §22.4a SLICE-2: a `fields:` block flows into the collection config ----

def test_parse_collection_manifest_reads_field_schema():
    doc = registry.parse_collection_manifest(
        "type: bdd\n"
        "fields:\n"
        "  priority:\n"
        "    type: enum\n"
        "    values: [P0, P1, P2]\n"
        "  tags:\n"
        "    type: tags\n"
    )
    assert doc.config["fields"] == {
        "priority": {"type": "enum", "values": ["P0", "P1", "P2"]},
        "tags": {"type": "tags"},
    }


def test_parse_collection_manifest_lenient_on_bad_field():
    # A bad field def is skipped (INV-3), the manifest still parses, and a
    # manifest with no surviving fields carries no `fields` config key at all.
    doc = registry.parse_collection_manifest(
        "type: bdd\n"
        "fields:\n"
        "  broken:\n"
        "    type: ref\n"
    )
    assert "fields" not in doc.config


# --- mirror discovery ---------------------------------------------------------


class _FakeGitea:
    """Minimal Gitea stub: projects.yaml in the registry repo + a content repo
    whose root holds a `features/` subdir carrying a `.collection.yaml`."""

    def __init__(self, projects_yaml: str, repo_tree: dict[str, dict[str, str]]):
        self._projects_yaml = projects_yaml
        self._repo_tree = repo_tree  # {repo: {path: text}}

    async def get_contents(self, org, repo, path, ref="main"):
        if path == "projects.yaml":
            return {"type": "file",
                    "content": base64.b64encode(self._projects_yaml.encode()).decode(),
                    "sha": "regsha-test"}
        text = self._repo_tree.get(repo, {}).get(path)
        if text is None:
            return None
        return {"type": "file",
                "content": base64.b64encode(text.encode()).decode(), "sha": "c0ffee"}

    async def list_dir(self, org, repo, path, ref="main"):
        # Root listing: surface each top-level segment as a 'dir' entry.
        prefix = (path.rstrip("/") + "/") if path else ""
        dirs = set()
        for p in self._repo_tree.get(repo, {}):
            if not p.startswith(prefix):
                continue
            rest = p[len(prefix):]
            if "/" in rest:
                dirs.add(rest.split("/", 1)[0])
        return [{"type": "dir", "name": n, "path": prefix + n} for n in sorted(dirs)]


_PROJECTS = (
    "deployment:\n  name: Ohm\n  tagline: t\n"
    "projects:\n  - id: ohm\n    name: Ohm\n    type: document\n"
    "    content_repo: ohm-rfc\n    visibility: public\n"
)


def test_refresh_registry_mirrors_named_collection():
    cfg = _db()
    gitea = _FakeGitea(
        projects_yaml=_PROJECTS,
        repo_tree={"ohm-rfc": {"features/.collection.yaml": "type: bdd\nname: Features\n"}},
    )
    asyncio.run(registry.refresh_registry(cfg, gitea))
    row = db.conn().execute(
        "SELECT type, subfolder, name, project_id, visibility FROM collections WHERE id='features'"
    ).fetchone()
    assert row is not None
    assert (row["type"], row["subfolder"], row["project_id"]) == ("bdd", "features", "ohm")
    assert row["name"] == "Features"
    # visibility inherits the project's (public) when the manifest omits it.
    assert row["visibility"] == "public"


def test_refresh_registry_leaves_default_collection_intact():
    cfg = _db()
    gitea = _FakeGitea(
        projects_yaml=_PROJECTS,
        repo_tree={"ohm-rfc": {"features/.collection.yaml": "type: bdd\n"}},
    )
    asyncio.run(registry.refresh_registry(cfg, gitea))
    # The default collection (from projects.yaml) and the named one coexist.
    ids = {r["id"] for r in db.conn().execute("SELECT id FROM collections")}
    assert {"default", "features"} <= ids


def test_refresh_registry_immutable_type_on_named_collection():
    cfg = _db()
    gitea = _FakeGitea(
        projects_yaml=_PROJECTS,
        repo_tree={"ohm-rfc": {"features/.collection.yaml": "type: bdd\n"}},
    )
    asyncio.run(registry.refresh_registry(cfg, gitea))
    # A later manifest that flips the type is refused (§22.4a immutable type).
    gitea._repo_tree["ohm-rfc"]["features/.collection.yaml"] = "type: document\n"
    asyncio.run(registry.refresh_registry(cfg, gitea))
    t = db.conn().execute("SELECT type FROM collections WHERE id='features'").fetchone()["type"]
    assert t == "bdd"
