"""Build a SQLite database from the vendored, validated upstream JSON.

    poetry run python -m dnd5e_db.build            # dist/dnd5e-2014-en-v5.10.0.sqlite3
    poetry run python -m dnd5e_db.build --edition 2024   # preview tier; also shipped

Hybrid schema: one table per collection with real columns for the record id
and name, plus the full validated record as JSON in `data` (query nested
fields with json_extract). A `metadata` table records upstream tag, edition,
and language. Validation runs first and the build refuses to write on any
drift. No timestamps: the same input produces a byte-identical file.
"""

from __future__ import annotations

import argparse
import json
import sqlite3
import sys
from pathlib import Path

from dnd5e_db.fetch import DATA_DIR, REPO_ROOT, UPSTREAM_TAG, UPSTREAM_REPO, vendored_tag
from dnd5e_db.models.base import SRDModel
from dnd5e_db.validate import EDITIONS, print_report, validate_corpus

DIST_DIR = REPO_ROOT / "dist"

# The SRD content itself is Wizards of the Coast's, published under CC-BY-4.0.
# Redistribution (this database included) must carry the attribution below.
SRD_ATTRIBUTION = {
    "2014": (
        "This work includes material taken from the System Reference Document 5.1"
        ' ("SRD 5.1") by Wizards of the Coast LLC, available at'
        " https://dnd.wizards.com/resources/systems-reference-document."
        " The SRD 5.1 is licensed under the Creative Commons Attribution 4.0"
        " International License, available at"
        " https://creativecommons.org/licenses/by/4.0/legalcode."
    ),
    "2024": (
        "This work includes material from the System Reference Document 5.2"
        ' ("SRD 5.2") by Wizards of the Coast LLC, available at'
        " https://www.dndbeyond.com/srd."
        " The SRD 5.2 is licensed under the Creative Commons Attribution 4.0"
        " International License, available at"
        " https://creativecommons.org/licenses/by/4.0/legalcode."
    ),
}


class DriftError(RuntimeError):
    """The vendored corpus does not match the models; nothing was written."""


def table_name(filename: str) -> str:
    """'5e-SRD-Weapon-Mastery-Properties.json' -> 'weapon_mastery_properties'"""
    stem = filename.removeprefix("5e-SRD-").removesuffix(".json")
    return stem.replace("-", "_").lower()


def db_filename(edition: str, language: str, tag: str) -> str:
    return f"dnd5e-{edition}-{language}-{tag}.sqlite3"


def build(
    edition: str = "2014",
    language: str = "en",
    out_dir: Path = DIST_DIR,
) -> Path:
    tag = vendored_tag()
    if tag != UPSTREAM_TAG:
        raise DriftError(
            f"data/ holds {tag!r} but pyproject pins {UPSTREAM_TAG!r};"
            " run: poetry run python -m dnd5e_db.fetch"
        )
    subdir, registry = EDITIONS[edition]
    data_dir = DATA_DIR / subdir.replace("en", language)

    report = validate_corpus(data_dir, registry)
    if not report.ok:
        print_report(report)
        raise DriftError(f"{data_dir} does not match the {edition} models; refusing to build")

    out_dir.mkdir(parents=True, exist_ok=True)
    db_path = out_dir / db_filename(edition, language, tag)
    db_path.unlink(missing_ok=True)

    conn = sqlite3.connect(db_path)
    try:
        conn.execute("CREATE TABLE metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL)")
        conn.executemany(
            "INSERT INTO metadata (key, value) VALUES (?, ?)",
            [
                ("source_repo", UPSTREAM_REPO),
                ("source_tag", tag),
                ("edition", edition),
                ("language", language),
                ("generator", "5e-database-sqlite"),
                ("license", "CC-BY-4.0"),
                ("attribution", SRD_ATTRIBUTION[edition]),
            ],
        )
        for fname in sorted(registry):
            table = table_name(fname)
            # name is nullable: e.g. 2014 levels have none
            conn.execute(
                f'CREATE TABLE "{table}" '
                '("index" TEXT PRIMARY KEY, name TEXT, data TEXT NOT NULL)'
            )
            conn.execute(f'CREATE INDEX "ix_{table}_name" ON "{table}" (name)')
            rows = [
                _row(registry[fname], record)
                for record in json.loads((data_dir / fname).read_text(encoding="utf-8"))
            ]
            conn.executemany(
                f'INSERT INTO "{table}" ("index", name, data) VALUES (:index, :name, :data)',
                rows,
            )
        conn.commit()
    finally:
        conn.close()

    return db_path


def _row(model: type[SRDModel], record: dict) -> dict:
    validated = model.model_validate(record)
    return {
        "index": record["index"],
        "name": record.get("name"),
        "data": validated.model_dump_json(by_alias=True, exclude_none=True),
    }


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--edition", choices=list(EDITIONS), default="2014")
    parser.add_argument("--language", default="en")
    parser.add_argument("--out", type=Path, default=DIST_DIR)
    args = parser.parse_args()
    try:
        db_path = build(args.edition, args.language, args.out)
    except DriftError as exc:
        print(f"error: {exc}", file=sys.stderr)
        sys.exit(1)
    size_kb = db_path.stat().st_size // 1024
    print(f"built {db_path} ({size_kb} KB)")


if __name__ == "__main__":
    main()
