"""§22 framework bug — migration-029 vs registry-mirror collection-id
divergence for a multi-project deployment's default project.

Migration 029 seeds the default project's collection id as the literal
'default' only when one project exists at migration time; with ≥2 projects it
falls back to the *project id*. The registry mirror expects the default
project to own the collection id 'default'. On an upgrade whose DB already
held ≥2 projects when 029 ran, the default project's collection is therefore
named after the project (e.g. 'ohm'), and the next mirror would INSERT a
second, empty 'default' collection. `reconcile_default_collection_id` heals
the divergence at startup, before the mirror, so the mirror merges instead of
duplicating. This is the collection-grain twin of `restamp_default_project`.
"""
from __future__ import annotations

import tempfile
from pathlib import Path

import app.db as db
from app import projects, registry

_TWO_PROJECT_REGISTRY = """
deployment:
  name: Open Human Model
  tagline: t
projects:
  - id: ohm
    name: Open Human Model
    type: document
    content_repo: ohm-content
    visibility: public
  - id: ecomm
    name: Ecomm
    type: bdd
    content_repo: ecomm-content
    visibility: public
"""


def _collection_ids_for(conn, project_id):
    return {r["id"] for r in conn.execute(
        "SELECT id FROM collections WHERE project_id = ?", (project_id,))}


class _Cfg:
    def __init__(self, path, default_id):
        self.database_path = path
        self.default_project_id = default_id


def _divergent_multiproject(monkeypatch, default_id="ohm"):
    """A DB in the post-029 divergent state: the default project ('ohm') owns a
    collection whose id is the project id (the 029 ≥2-projects seed), a second
    project ('ecomm') owns its own collection, and NO 'default' collection
    exists. Entry rows point at the divergent collection_id='ohm'."""
    path = str(Path(tempfile.mkdtemp()) / "t.db")
    cfg = _Cfg(path, default_id)
    db.run_migrations(cfg)  # seeds bootstrap 'default' project + 'default' collection
    monkeypatch.setattr(db, "_CONN", db.connect(path))
    conn = db.conn()
    # Drop the single-project bootstrap seed and rebuild the divergent
    # multi-project state 029 would have produced on an upgrade.
    conn.execute("DELETE FROM collections")
    conn.execute("DELETE FROM projects")
    conn.execute("INSERT INTO projects (id,name,content_repo,visibility) "
                 "VALUES ('ohm','Open Human Model','ohm-content','public')")
    conn.execute("INSERT INTO projects (id,name,content_repo,visibility) "
                 "VALUES ('ecomm','Ecomm','ecomm-content','public')")
    # 029 ≥2-projects seed: collection id == project id.
    conn.execute("INSERT INTO collections (id,project_id,type,subfolder,initial_state,visibility,name) "
                 "VALUES ('ohm','ohm','document','','super-draft','public','Open Human Model')")
    conn.execute("INSERT INTO collections (id,project_id,type,subfolder,initial_state,visibility,name) "
                 "VALUES ('ecomm','ecomm','bdd','ecomm','super-draft','public','Ecomm')")
    conn.execute("INSERT INTO users (id,gitea_login,display_name,role) VALUES (1,'a','A','contributor')")
    # Entry data for the default project lives in the divergent 'ohm' collection.
    conn.execute("INSERT INTO cached_rfcs (slug,title,state,collection_id) VALUES ('human','Human','active','ohm')")
    conn.execute("INSERT INTO rfc_collaborators (rfc_slug,user_id,role_in_rfc,collection_id) "
                 "VALUES ('human',1,'contributor','ohm')")
    conn.execute("INSERT INTO stars (user_id,rfc_slug,collection_id) VALUES (1,'human','ohm')")
    # The 'ecomm' collection has its own entry.
    conn.execute("INSERT INTO cached_rfcs (slug,title,state,collection_id) VALUES ('cart','Cart','active','ecomm')")
    return cfg, conn


def test_reconcile_renames_divergent_default_collection_to_default(monkeypatch):
    cfg, conn = _divergent_multiproject(monkeypatch, default_id="ohm")
    projects.reconcile_default_collection_id(cfg)
    # The default project's collection id is now the canonical 'default'.
    assert conn.execute("SELECT 1 FROM collections WHERE id='ohm'").fetchone() is None
    row = conn.execute("SELECT project_id FROM collections WHERE id='default'").fetchone()
    assert row is not None and row["project_id"] == "ohm"
    # Entry rows cascaded onto 'default'.
    assert conn.execute("SELECT collection_id FROM cached_rfcs WHERE slug='human'").fetchone()["collection_id"] == "default"
    assert conn.execute("SELECT collection_id FROM rfc_collaborators WHERE rfc_slug='human'").fetchone()["collection_id"] == "default"
    assert conn.execute("SELECT collection_id FROM stars WHERE rfc_slug='human'").fetchone()["collection_id"] == "default"
    # The non-default 'ecomm' collection is untouched (mirror + 029 agree on it).
    assert conn.execute("SELECT 1 FROM collections WHERE id='ecomm'").fetchone() is not None
    assert conn.execute("SELECT collection_id FROM cached_rfcs WHERE slug='cart'").fetchone()["collection_id"] == "ecomm"
    # FK integrity intact after the rename.
    assert conn.execute("PRAGMA foreign_key_check").fetchall() == []


def test_reconcile_is_idempotent(monkeypatch):
    cfg, conn = _divergent_multiproject(monkeypatch, default_id="ohm")
    projects.reconcile_default_collection_id(cfg)
    projects.reconcile_default_collection_id(cfg)  # second call: already aligned → no-op
    assert conn.execute("SELECT project_id FROM collections WHERE id='default'").fetchone()["project_id"] == "ohm"
    assert conn.execute("SELECT COUNT(*) c FROM cached_rfcs WHERE collection_id='default'").fetchone()["c"] == 1


def test_reconcile_noop_when_default_id_is_literal_default(monkeypatch):
    # Single-project deployment, no DEFAULT_PROJECT_ID: 029 already seeded
    # 'default' and the mirror agrees — nothing to reconcile.
    path = str(Path(tempfile.mkdtemp()) / "t.db")
    cfg = _Cfg(path, "")  # resolves to 'default'
    db.run_migrations(cfg)
    monkeypatch.setattr(db, "_CONN", db.connect(path))
    conn = db.conn()
    projects.reconcile_default_collection_id(cfg)
    assert conn.execute("SELECT 1 FROM collections WHERE id='default'").fetchone() is not None


def test_mirror_duplicates_without_reconcile(monkeypatch):
    # Demonstrates the bug: the mirror on the divergent state inserts a SECOND
    # 'default' collection for the default project (alongside the 029 'ohm').
    cfg, conn = _divergent_multiproject(monkeypatch, default_id="ohm")
    doc = registry.parse_registry(_TWO_PROJECT_REGISTRY)
    registry.apply_registry(doc, registry_sha="s1", default_id="ohm")
    assert _collection_ids_for(conn, "ohm") == {"ohm", "default"}  # duplicate!


def test_reconcile_then_mirror_merges_no_duplicate(monkeypatch):
    # With the fix: reconcile before the mirror → the mirror merges onto the
    # canonical 'default' collection; the default project owns exactly one.
    cfg, conn = _divergent_multiproject(monkeypatch, default_id="ohm")
    projects.reconcile_default_collection_id(cfg)
    doc = registry.parse_registry(_TWO_PROJECT_REGISTRY)
    registry.apply_registry(doc, registry_sha="s1", default_id="ohm")
    assert _collection_ids_for(conn, "ohm") == {"default"}
    assert _collection_ids_for(conn, "ecomm") == {"ecomm"}
    # The default project's corpus entry is intact under 'default'.
    assert conn.execute("SELECT collection_id FROM cached_rfcs WHERE slug='human'").fetchone()["collection_id"] == "default"
    # The mirror refreshed the merged collection's metadata (type from registry).
    assert conn.execute("SELECT type FROM collections WHERE id='default'").fetchone()["type"] == "document"


def test_reconcile_skips_when_default_collection_already_exists(monkeypatch):
    # A prior buggy mirror already created a 'default' collection alongside the
    # divergent 'ohm' one: don't auto-merge data — leave both for operator cleanup.
    cfg, conn = _divergent_multiproject(monkeypatch, default_id="ohm")
    conn.execute("INSERT INTO collections (id,project_id,type,subfolder,initial_state,visibility,name) "
                 "VALUES ('default','ohm','document','','super-draft','public','dup')")
    projects.reconcile_default_collection_id(cfg)
    # Both still present (no destructive auto-merge).
    assert conn.execute("SELECT 1 FROM collections WHERE id='ohm'").fetchone() is not None
    assert conn.execute("SELECT 1 FROM collections WHERE id='default'").fetchone() is not None
