#!/usr/bin/env python3
"""Deterministic generator for the 'megalith' large test codebase.

Regenerate the whole tree with:  python3 tools/generate.py
Everything is seeded, so output is identical on every run.

Produces:
  src/megalith/core/...              shared utilities all domains import
  src/megalith/<domain>/mod_XX.py    NUM_DOMAINS x MODULES_PER_DOMAIN modules
  src/megalith/<domain>/__init__.py
  tests/test_<domain>.py             one test file per domain
  docs/<domain>.md                   generated docs
  data/<domain>.json                 fixture data
"""
from __future__ import annotations

import json
import random
import shutil
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "src" / "megalith"
TESTS = ROOT / "tests"
DOCS = ROOT / "docs"
DATA = ROOT / "data"

NUM_DOMAINS = 24
MODULES_PER_DOMAIN = 18
FUNCS_PER_MODULE = 3

DOMAINS = [
    "inventory", "shipping", "billing", "accounts", "catalog", "pricing",
    "routing", "telemetry", "auditing", "contracts", "customs", "dispatch",
    "forecast", "insurance", "ledger", "manifest", "notifications", "packaging",
    "payments", "quotas", "returns", "scheduling", "tracking", "warehouse",
][:NUM_DOMAINS]

ADJECTIVES = ["swift", "stable", "batch", "prime", "delta", "rapid", "solid", "lean"]
NOUNS = ["rate", "index", "weight", "score", "factor", "margin", "load", "span"]


def write(path: Path, content: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")


def gen_core() -> None:
    write(SRC / "__init__.py", '"""megalith: generated large test codebase."""\n\n__version__ = "1.0.0"\n')

    write(SRC / "core" / "__init__.py", '"""Core utilities shared by every domain."""\n')

    write(SRC / "core" / "math_utils.py", '''"""Shared arithmetic helpers used across all generated domains."""
from __future__ import annotations


def clamp(value: float, low: float, high: float) -> float:
    """Clamp value into [low, high]."""
    if low > high:
        raise ValueError("low must be <= high")
    return max(low, min(high, value))


def weighted_mix(a: float, b: float, weight_a: int, weight_b: int) -> float:
    """Weighted average of a and b."""
    total = weight_a + weight_b
    if total == 0:
        raise ValueError("weights must not both be zero")
    return (a * weight_a + b * weight_b) / total


def cyclic_hash(x: int, y: int, mult: int, add: int, mod: int) -> int:
    """The deterministic kernel every generated compute function calls."""
    if mod <= 0:
        raise ValueError("mod must be positive")
    return (x * mult + y * add) % mod
''')

    write(SRC / "core" / "text_utils.py", '''"""Shared text helpers used across all generated domains."""
from __future__ import annotations


def slugify(text: str) -> str:
    """Lowercase, spaces to hyphens, strip non-alphanumerics."""
    out = []
    for ch in text.lower().strip():
        if ch.isalnum():
            out.append(ch)
        elif ch in (" ", "-", "_"):
            out.append("-")
    slug = "".join(out)
    while "--" in slug:
        slug = slug.replace("--", "-")
    return slug.strip("-")


def label(domain: str, name: str, index: int) -> str:
    """Canonical display label for a generated record."""
    return f"{domain}:{name}#{index:04d}"
''')

    write(SRC / "core" / "registry.py", '''"""Process-wide registry that generated modules register themselves into."""
from __future__ import annotations

from typing import Callable, Dict

_HANDLERS: Dict[str, Callable[..., object]] = {}


def register(key: str, fn: Callable[..., object]) -> None:
    if key in _HANDLERS:
        raise KeyError(f"duplicate registry key: {key}")
    _HANDLERS[key] = fn


def lookup(key: str) -> Callable[..., object]:
    return _HANDLERS[key]


def keys() -> list:
    return sorted(_HANDLERS)


def size() -> int:
    return len(_HANDLERS)
''')


def module_source(domain: str, mod_idx: int, rng: random.Random) -> tuple[str, list]:
    """Return (source, [(func_name, mult, add, mod, sample_x, sample_y, expected)])."""
    funcs = []
    lines = [
        f'"""Generated module {domain}.mod_{mod_idx:02d} — deterministic compute helpers."""',
        "from __future__ import annotations",
        "",
        "from dataclasses import dataclass",
        "",
        "from megalith.core.math_utils import clamp, cyclic_hash",
        "from megalith.core.text_utils import label",
        "",
        "",
        f"DOMAIN = {domain!r}",
        f"MODULE_INDEX = {mod_idx}",
        "",
        "",
        "@dataclass",
        f"class {domain.capitalize()}Record{mod_idx:02d}:",
        f'    """One record in the {domain} domain."""',
        "",
        "    ident: int",
        "    name: str",
        "    value: float",
        "",
        "    def display(self) -> str:",
        "        return label(DOMAIN, self.name, self.ident)",
        "",
        "    def normalized(self) -> float:",
        "        return clamp(self.value, 0.0, 100.0)",
        "",
    ]

    for f_idx in range(FUNCS_PER_MODULE):
        adj = rng.choice(ADJECTIVES)
        noun = rng.choice(NOUNS)
        fname = f"compute_{adj}_{noun}_{f_idx}"
        mult = rng.randint(3, 97)
        add = rng.randint(3, 97)
        mod = rng.choice([101, 211, 307, 401, 503, 601, 701])
        sample_x = rng.randint(1, 500)
        sample_y = rng.randint(1, 500)
        expected = (sample_x * mult + sample_y * add) % mod
        funcs.append((fname, mult, add, mod, sample_x, sample_y, expected))

        lines += [
            "",
            f"def {fname}(x: int, y: int) -> int:",
            f'    """Deterministic {adj} {noun} for the {domain} domain."""',
            f"    return cyclic_hash(x, y, mult={mult}, add={add}, mod={mod})",
            "",
        ]

    reg_lines = [
        "",
        "def register_handlers(register) -> None:",
        '    """Register this module\'s compute functions into a registry."""',
    ]
    for (fname, *_rest) in funcs:
        key = f"{domain}.m{mod_idx:02d}.{fname}"
        reg_lines.append(f"    register({key!r}, {fname})")
    lines += reg_lines + [""]

    return "\n".join(lines), funcs


def gen_domain(domain: str, rng: random.Random) -> dict:
    """Generate one domain package. Returns manifest info for tests/docs/data."""
    pkg = SRC / domain
    manifest = {"domain": domain, "modules": []}

    init_lines = [
        f'"""Generated domain package: {domain} ({MODULES_PER_DOMAIN} modules)."""',
        "",
    ]
    for m in range(MODULES_PER_DOMAIN):
        init_lines.append(f"from . import mod_{m:02d}  # noqa: F401")
    init_lines.append("")
    write(pkg / "__init__.py", "\n".join(init_lines))

    for m in range(MODULES_PER_DOMAIN):
        source, funcs = module_source(domain, m, rng)
        write(pkg / f"mod_{m:02d}.py", source)
        manifest["modules"].append(
            {
                "module": f"mod_{m:02d}",
                "functions": [
                    {
                        "name": fname,
                        "mult": mult,
                        "add": add,
                        "mod": mod,
                        "sample": {"x": sx, "y": sy, "expected": expected},
                    }
                    for (fname, mult, add, mod, sx, sy, expected) in funcs
                ],
            }
        )
    return manifest


def gen_tests(manifest: dict) -> None:
    domain = manifest["domain"]
    lines = [
        f'"""Generated tests for the {domain} domain."""',
        "import unittest",
        "",
    ]
    for mod in manifest["modules"][:6]:  # test a sample of modules per domain
        lines.append(f"from megalith.{domain} import {mod['module']} as {mod['module']}")
    lines += [
        "",
        "",
        f"class Test{domain.capitalize()}(unittest.TestCase):",
    ]
    for mod in manifest["modules"][:6]:
        for fn in mod["functions"]:
            s = fn["sample"]
            lines += [
                f"    def test_{mod['module']}_{fn['name']}(self):",
                f"        self.assertEqual({mod['module']}.{fn['name']}({s['x']}, {s['y']}), {s['expected']})",
                "",
            ]
    # Record smoke test
    first = manifest["modules"][0]["module"]
    cls = f"{domain.capitalize()}Record00"
    lines += [
        f"    def test_record_display_and_normalize(self):",
        f"        rec = {first}.{cls}(ident=7, name='sample', value=250.0)",
        f"        self.assertEqual(rec.display(), '{domain}:sample#0007')",
        f"        self.assertEqual(rec.normalized(), 100.0)",
        "",
        "",
        'if __name__ == "__main__":',
        "    unittest.main()",
        "",
    ]
    write(TESTS / f"test_{domain}.py", "\n".join(lines))


def gen_docs_and_data(manifest: dict, rng: random.Random) -> None:
    domain = manifest["domain"]
    total_funcs = sum(len(m["functions"]) for m in manifest["modules"])
    doc = [
        f"# Domain: {domain}",
        "",
        f"Auto-generated domain with {len(manifest['modules'])} modules and "
        f"{total_funcs} compute functions.",
        "",
        "## Modules",
        "",
    ]
    for mod in manifest["modules"]:
        doc.append(f"### `megalith.{domain}.{mod['module']}`")
        doc.append("")
        for fn in mod["functions"]:
            doc.append(
                f"- `{fn['name']}(x, y)` — cyclic hash with mult={fn['mult']}, "
                f"add={fn['add']}, mod={fn['mod']}"
            )
        doc.append("")
    write(DOCS / f"{domain}.md", "\n".join(doc))

    records = [
        {
            "id": i,
            "name": f"{rng.choice(ADJECTIVES)}-{rng.choice(NOUNS)}-{i}",
            "value": round(rng.uniform(0, 500), 2),
            "active": rng.random() > 0.3,
        }
        for i in range(40)
    ]
    write(DATA / f"{domain}.json", json.dumps({"domain": domain, "records": records}, indent=2))


def gen_test_runner_helper() -> None:
    write(TESTS / "__init__.py", "")
    write(ROOT / "run_tests.sh", """#!/usr/bin/env bash
# Run the full generated test suite.
cd "$(dirname "$0")"
PYTHONPATH=src python3 -m unittest discover -s tests "$@"
""")
    (ROOT / "run_tests.sh").chmod(0o755)


def main() -> None:
    rng = random.Random(20260725)

    for stale in (SRC, TESTS, DOCS, DATA):
        if stale.exists():
            shutil.rmtree(stale)

    gen_core()
    manifests = []
    for domain in DOMAINS:
        manifest = gen_domain(domain, rng)
        manifests.append(manifest)
        gen_tests(manifest)
        gen_docs_and_data(manifest, rng)

    gen_test_runner_helper()

    total_modules = sum(len(m["modules"]) for m in manifests)
    total_files = sum(1 for _ in ROOT.rglob("*") if _.is_file())
    print(f"Generated {len(manifests)} domains, {total_modules} modules, "
          f"{total_files} files total under {ROOT}")


if __name__ == "__main__":
    main()
