"""SLICE-4 — single-entry metadata edit endpoint (PUC-1, §6.4).

Through the real API: contributor+ gating (INV-4), schema validation at the
write boundary, direct commit to the sidecar with lazy migration, re-ingest,
and the GET RFC `meta` + `can_edit_meta` exposure. Reuses the fake-Gitea harness.
"""
from __future__ import annotations

import asyncio
import json

import yaml
from fastapi.testclient import TestClient

from app import cache, db, gitea as gitea_mod
from app.config import load_config

from test_propose_vertical import (  # noqa: F401 (fixtures)
    app_with_fake_gitea,
    provision_user_row,
    sign_in_as,
    tmp_env,
)

PID = "default"
CID = "default"
META = f"/api/projects/{PID}/collections/{CID}"


def _refresh():
    cfg = load_config()
    asyncio.run(cache.refresh_meta_repo(cfg, gitea_mod.Gitea(cfg)))


def _set_fields(schema):
    db.conn().execute(
        "UPDATE collections SET config_json = ? WHERE id = 'default'",
        (json.dumps({"fields": schema}),))


def _seed_legacy(fake, slug, *, state="active", **front):
    fm = {"slug": slug, "title": slug.title(), "state": state, **front}
    body = yaml.safe_dump(fm, sort_keys=False).strip()
    fake.files[("wiggleverse", "meta", "main", f"rfcs/{slug}.md")] = {
        "content": f"---\n{body}\n---\n\nBody.\n", "sha": slug}


def _seed_migrated(fake, slug, sidecar):
    fake.files[("wiggleverse", "meta", "main", f"rfcs/{slug}.md")] = {
        "content": "Body.\n", "sha": f"{slug}-md"}
    fake.files[("wiggleverse", "meta", "main", f"rfcs/{slug}.meta.yaml")] = {
        "content": sidecar, "sha": f"{slug}-sc"}


def _login_owner(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")


def test_edit_meta_sets_value_commits_sidecar_and_lazy_migrates(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _set_fields({"priority": {"type": "enum", "values": ["P0", "P1", "P2"]},
                     "tags": {"type": "tags"}})
        _seed_legacy(fake, "a", priority="P1", tags=["x"])
        _refresh()
        _login_owner(client)
        r = client.post(f"{META}/rfcs/a/meta", json={"values": {"priority": "P0"}})
        assert r.status_code == 200, r.text
        assert r.json()["meta"]["priority"] == "P0"
    # sidecar written, .md lazy-migrated to body-only
    sc = yaml.safe_load(
        fake.files[("wiggleverse", "meta", "main", "rfcs/a.meta.yaml")]["content"])
    assert sc["priority"] == "P0"
    assert "---" not in fake.files[("wiggleverse", "meta", "main", "rfcs/a.md")]["content"]
    # cache reflects the new value
    row = db.conn().execute(
        "SELECT meta_json FROM cached_rfcs WHERE slug='a'").fetchone()
    assert json.loads(row["meta_json"])["priority"] == "P0"


def test_edit_meta_rejects_value_outside_enum(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _set_fields({"priority": {"type": "enum", "values": ["P0", "P1"]}})
        _seed_legacy(fake, "a", priority="P1")
        _refresh()
        _login_owner(client)
        r = client.post(f"{META}/rfcs/a/meta", json={"values": {"priority": "ZZZ"}})
        assert r.status_code == 422, r.text
        # nothing committed
        assert ("wiggleverse", "meta", "main", "rfcs/a.meta.yaml") not in fake.files


def test_edit_meta_rejects_unknown_field(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _set_fields({"priority": {"type": "enum", "values": ["P0"]}})
        _seed_legacy(fake, "a", priority="P0")
        _refresh()
        _login_owner(client)
        r = client.post(f"{META}/rfcs/a/meta", json={"values": {"nope": "x"}})
        assert r.status_code == 422, r.text


def test_edit_meta_forbidden_for_anonymous(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _set_fields({"priority": {"type": "enum", "values": ["P0"]}})
        _seed_legacy(fake, "a", priority="P0")
        _refresh()
        r = client.post(f"{META}/rfcs/a/meta", json={"values": {"priority": "P0"}})
        assert r.status_code == 403, r.text


def test_edit_meta_on_already_migrated_entry(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _set_fields({"priority": {"type": "enum", "values": ["P0", "P1"]}})
        _seed_migrated(fake, "a", "slug: a\ntitle: A\nstate: active\npriority: P1\n")
        _refresh()
        _login_owner(client)
        r = client.post(f"{META}/rfcs/a/meta", json={"values": {"priority": "P0"}})
        assert r.status_code == 200, r.text
    sc = yaml.safe_load(
        fake.files[("wiggleverse", "meta", "main", "rfcs/a.meta.yaml")]["content"])
    assert sc["priority"] == "P0"
    # .md untouched (still body-only)
    assert fake.files[("wiggleverse", "meta", "main", "rfcs/a.md")]["content"] == "Body.\n"


def test_get_rfc_exposes_meta_and_can_edit(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _set_fields({"priority": {"type": "enum", "values": ["P0", "P1"]}})
        _seed_legacy(fake, "a", priority="P1")
        _refresh()
        # anonymous: meta present, can_edit_meta False
        r = client.get(f"{META}/rfcs/a")
        assert r.status_code == 200, r.text
        assert r.json()["meta"]["priority"] == "P1"
        assert r.json()["can_edit_meta"] is False
        # owner: can_edit_meta True
        _login_owner(client)
        r = client.get(f"{META}/rfcs/a")
        assert r.json()["can_edit_meta"] is True


# ---- Owner-gated collection migrate endpoint (PUC-5) ----

def test_migrate_collection_endpoint_owner(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _seed_legacy(fake, "a", priority="P1", tags=["x"])
        _seed_legacy(fake, "b", priority="P0")
        _refresh()
        _login_owner(client)
        r = client.post(f"{META}/migrate")
        assert r.status_code == 200, r.text
        assert r.json()["committed"] is True
        assert set(r.json()["migrated"]) == {"a", "b"}
    # both entries now body-only + sidecar
    for slug in ("a", "b"):
        assert "---" not in fake.files[("wiggleverse", "meta", "main", f"rfcs/{slug}.md")]["content"]
        assert ("wiggleverse", "meta", "main", f"rfcs/{slug}.meta.yaml") in fake.files


def test_migrate_collection_forbidden_for_contributor(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _seed_legacy(fake, "a", priority="P1")
        _refresh()
        provision_user_row(user_id=2, login="carol", role="contributor")
        sign_in_as(client, user_id=2, gitea_login="carol",
                   display_name="Carol", role="contributor")
        r = client.post(f"{META}/migrate")
        assert r.status_code == 403, r.text


def test_migrate_collection_idempotent(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _seed_legacy(fake, "a", priority="P1")
        _refresh()
        _login_owner(client)
        assert client.post(f"{META}/migrate").json()["committed"] is True
        # second run: nothing left to migrate
        r2 = client.post(f"{META}/migrate")
        assert r2.status_code == 200, r2.text
        assert r2.json()["committed"] is False
