"""Project registry — the §22 multi-project layer.

A deployment hosts one or more projects (§22.1). The git registry mirror
that lets a deployment declare projects lands in M3 and drives this module.
`seed_default_project` (the §22.13 META_REPO backfill) is retired in M3;
the registry mirror (`registry.refresh_registry`) is authoritative.
"""
from __future__ import annotations

import logging

from . import db
from .config import Config

log = logging.getLogger(__name__)

DEFAULT_PROJECT_ID = "default"


def resolved_default_id(config: Config) -> str:
    """The id of the deployment's bootstrap/default project. Plan A: always
    'default' (the re-stamp to a config slug rides Plan B). The config knob is
    read here so Plan B can flip the resolution without touching call sites."""
    return config.default_project_id.strip() or DEFAULT_PROJECT_ID


def restamp_default_project(config: Config) -> None:
    """§22.13 step 1 — one-time rename of the M1 bootstrap project id
    (DEFAULT_PROJECT_ID = 'default') to the deployment's configured default id
    (the DEFAULT_PROJECT_ID env var, e.g. 'ohm'), so the deployment's original
    corpus lands at a meaningful `/p/<id>/` and `default` is never a public URL.

    Renames `project_id` across every project-scoped table (discovered by
    column, so it stays correct as the schema grows), then drops the stale
    bootstrap `projects` row (its data has moved to the configured row, which
    the registry mirror already created). Idempotent and a no-op when the
    configured id is still 'default' or no bootstrap rows remain. Runs at
    startup after the registry mirror, with FK enforcement off for the rename
    (the composite FKs are kept consistent because parent and child rows are
    renamed together) and a foreign_key_check backstop before commit.
    """
    target = resolved_default_id(config)
    if target == DEFAULT_PROJECT_ID:
        return
    conn = db.conn()
    # §22 three-tier: the entry-corpus tables key on collection_id now; detect a
    # lingering bootstrap project by the project-grain `collections.project_id`
    # (the PRAGMA scan below still renames every project_id column dynamically).
    has_rows = conn.execute(
        "SELECT 1 FROM collections WHERE project_id = ? LIMIT 1", (DEFAULT_PROJECT_ID,)
    ).fetchone()
    stale_proj = conn.execute(
        "SELECT 1 FROM projects WHERE id = ? LIMIT 1", (DEFAULT_PROJECT_ID,)
    ).fetchone()
    if not has_rows and not stale_proj:
        return
    if conn.execute("SELECT 1 FROM projects WHERE id = ? LIMIT 1", (target,)).fetchone() is None:
        log.warning("restamp: target project %r not in registry yet; skipping", target)
        return

    tables = [r["name"] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'")]
    pid_tables = [
        t for t in tables
        if any(c["name"] == "project_id" for c in conn.execute(f"PRAGMA table_info({t})"))
    ]
    conn.execute("PRAGMA foreign_keys = OFF")
    try:
        conn.execute("BEGIN")
        for t in pid_tables:
            conn.execute(
                f"UPDATE {t} SET project_id = ? WHERE project_id = ?",
                (target, DEFAULT_PROJECT_ID),
            )
        # The bootstrap row's data has moved to the configured (registry) row.
        conn.execute("DELETE FROM projects WHERE id = ?", (DEFAULT_PROJECT_ID,))
        violations = conn.execute("PRAGMA foreign_key_check").fetchall()
        if violations:
            conn.execute("ROLLBACK")
            raise RuntimeError(
                f"restamp left foreign-key violations: {[tuple(v) for v in violations]}"
            )
        conn.execute("COMMIT")
    except Exception:
        try:
            conn.execute("ROLLBACK")
        except Exception:
            pass
        raise
    finally:
        conn.execute("PRAGMA foreign_keys = ON")
    log.info("restamp: renamed bootstrap project %r -> %r across %d tables",
             DEFAULT_PROJECT_ID, target, len(pid_tables))


def default_content_repo(config: Config) -> str | None:
    """The content repo the single-corpus mirror reads, from the default
    project's row (filled by the registry mirror). Replaces the retired
    META_REPO. None until the registry mirror has run."""
    row = db.conn().execute(
        "SELECT content_repo FROM projects WHERE id = ?",
        (resolved_default_id(config),),
    ).fetchone()
    return row["content_repo"] if row and row["content_repo"] else None


def content_repo(project_id: str) -> str | None:
    """The content repo for a specific project (§22.3). None if unknown/unset.
    The per-project successor to `default_content_repo` for the write path."""
    row = db.conn().execute(
        "SELECT content_repo FROM projects WHERE id = ?", (project_id,)
    ).fetchone()
    return row["content_repo"] if row and row["content_repo"] else None


def project_initial_state(project_id: str) -> str:
    """§22.4b landing state for new entries in a project's default collection
    (the per-corpus field moved down to the collection in migration 029).
    Defaults to 'super-draft' for an unknown/unset row (today's-flow default)."""
    from . import collections as collections_mod
    return collections_mod.collection_initial_state(
        collections_mod.default_collection_id(project_id)
    )
