from __future__ import annotations

import argparse
import ast
import json
import re
from pathlib import Path
from typing import Any


ROOT = Path(__file__).resolve().parents[1]
OUTPUT = ROOT / "config" / "architecture-inventory.json"


def relative(path: Path) -> str:
    return path.relative_to(ROOT).as_posix()


def source(path: str) -> str:
    return (ROOT / path).read_text(encoding="utf-8")


def page_inventory(html: str) -> list[dict[str, str]]:
    items: list[dict[str, str]] = []
    seen: set[str] = set()
    pattern = re.compile(
        r'<button[^>]+data-view="(?P<id>[^"]+)"[^>]+title="(?P<title>[^"]+)"',
        re.IGNORECASE,
    )
    for match in pattern.finditer(html):
        page_id = match.group("id")
        if page_id in seen:
            continue
        seen.add(page_id)
        items.append({"id": page_id, "title": match.group("title")})
    return items


def api_inventory(server: str) -> dict[str, list[str]]:
    routes = json.loads(source("config/api.config.json"))["routes"]
    exact = sorted(
        {item["path"] for item in routes if item["match"] == "exact"}
    )
    prefixes = sorted(
        set(re.findall(r'parsed\.path\.startswith\(\s*"(/api/[^"]+)"', server))
    )
    patterns = sorted(
        {item["path"] for item in routes if item["match"] == "regex"}
    )
    return {"exact": exact, "prefixes": prefixes, "patterns": patterns}


def database_inventory(sources: list[str]) -> list[str]:
    tables: list[str] = []
    for database in sources:
        tables.extend(
            re.findall(
                r"CREATE TABLE IF NOT EXISTS\s+([a-zA-Z0-9_]+)", database
            )
        )
    return list(dict.fromkeys(tables))


def python_functions(path: str, prefixes: tuple[str, ...]) -> list[str]:
    tree = ast.parse(source(path), filename=path)
    result: list[str] = []
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith(prefixes):
            result.append(node.name)
    return sorted(set(result))


def css_layers(html: str) -> list[str]:
    return re.findall(r'<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"', html)


def frontend_fragments(registry: str) -> list[str]:
    return re.findall(
        r'^\s*\["[^"]+", "(/pages/[^"]+/page\.html(?:\?[^"]*)?)", \[',
        registry,
        re.MULTILINE,
    )


def source_metrics(path: Path) -> dict[str, int]:
    text = path.read_text(encoding="utf-8")
    return {
        "bytes": len(text.encode("utf-8")),
        "lines": len(text.splitlines()),
    }


def code_hotspots() -> list[dict[str, Any]]:
    candidates = [
        "backend/application.py",
        "backend/http/dispatch.py",
        "backend/features/system/service.py",
        "backend/features/accounts/application.py",
        "backend/jobs/service.py",
        "backend/jobs/refresh.py",
        "database.py",
        "backend/features/screener/engine.py",
        "backend/features/screener/catalog.py",
        "backend/features/screener/data_sync.py",
        "backend/features/screener/indicators.py",
        "backend/features/screener/factors.py",
        "backend/features/screener/formula.py",
        "backend/features/screener/regime.py",
        "backend/features/screener/selection.py",
        "backend/features/screener/backtest.py",
        "backend/features/market/insights.py",
        "backend/features/market/insights_context.py",
        "backend/features/market/insights_auction_scoring.py",
        "backend/features/market/insights_auction_data.py",
        "backend/features/market/insights_auction.py",
        "backend/features/market/insights_themes.py",
        "backend/features/market/insights_popularity.py",
        "backend/data/providers/tushare_client.py",
        "backend/data/providers/tushare_transport.py",
        "backend/data/providers/tushare_helpers.py",
        "backend/data/providers/tushare_dashboard.py",
        "backend/data/providers/tushare_indices.py",
        "backend/data/providers/tushare_industries.py",
        "backend/data/providers/tushare_sectors.py",
        "backend/data/providers/tushare_dragon_tiger.py",
        "backend/data/providers/tushare_stocks.py",
        "backend/data/providers/tushare_daily.py",
        "backend/features/heaven/service.py",
        "backend/features/heaven/manual.py",
        "backend/features/heaven/trend.py",
        "backend/features/heaven/market_context.py",
        "backend/features/heaven/readings.py",
        "backend/features/heaven/engine.py",
        "frontend/index.html",
        "frontend/bootstrap.js",
        "frontend/pages.config.js",
        "frontend/app.js",
        "frontend/shared/context.js",
        "frontend/shared/application.js",
        "frontend/shared/feedback.js",
        "frontend/shared/dashboard.js",
        "frontend/shared/session.js",
        "frontend/shared/admin.js",
        "frontend/shared/theme.js",
        "frontend/shared/table.js",
        "frontend/pages/heaven/foundation.css",
        "frontend/pages/screener/foundation.css",
        "frontend/pages/auction/foundation.css",
        "frontend/shared/shell.css",
        "frontend/pages/heaven/page.js",
    ]
    candidates.extend(
        relative(path)
        for path in sorted((ROOT / "frontend" / "pages" / "market").glob("*.js"))
    )
    candidates.extend(
        relative(path)
        for path in sorted((ROOT / "backend" / "features").glob("*/routes.py"))
    )
    candidates.extend(
        relative(path)
        for path in sorted((ROOT / "frontend" / "pages").glob("*/page.html"))
    )
    rows = []
    for name in candidates:
        path = ROOT / name
        if not path.is_file():
            continue
        rows.append({"path": name, **source_metrics(path)})
    return sorted(rows, key=lambda item: item["bytes"], reverse=True)


def build() -> dict[str, Any]:
    html = source("frontend/index.html")
    page_registry = source("frontend/pages.config.js")
    fragments = frontend_fragments(page_registry)
    http_sources = [source("backend/http/dispatch.py")]
    http_sources.extend(
        path.read_text(encoding="utf-8")
        for path in sorted((ROOT / "backend" / "features").glob("*/routes.py"))
    )
    server = "\n".join(http_sources)
    database_sources = [source("database.py")]
    database_sources.extend(
        path.read_text(encoding="utf-8")
        for path in sorted((ROOT / "backend" / "database" / "migrations").glob("m*.py"))
    )
    database_sources.append(
        source("backend/database/migrations/runner.py")
    )
    pages = page_inventory(html)
    api = api_inventory(server)
    tables = database_inventory(database_sources)
    return {
        "schema_version": 1,
        "captured_from": "app accepted modular runtime",
        "runtime": {
            "http_server": "http.server.ThreadingHTTPServer",
            "application_processes": 1,
            "database": "SQLite WAL",
            "frontend": "build-free HTML/CSS/JavaScript",
            "container_port": 8765,
        },
        "counts": {
            "primary_pages": len(pages),
            "api_exact_paths": len(api["exact"]),
            "api_prefixes": len(api["prefixes"]),
            "api_patterns": len(api["patterns"]),
            "database_tables": len(tables),
            "frontend_page_fragments": len(fragments),
        },
        "pages": pages,
        "api": api,
        "database_tables": tables,
        "background_job_methods": python_functions(
            "backend/jobs/service.py",
            ("_background", "_run_background", "_schedule_", "run_automatic"),
        ),
        "external_data_adapters": [
            {"provider": "tushare", "path": "backend/data/providers/tushare_client.py", "runtime_role": "stable client facade for primary deterministic market data"},
            {"provider": "datahub", "path": "backend/data/datahub/client.py", "runtime_role": "optional official EOD read path behind per-dataset flags"},
            {"provider": "ifind", "path": "backend/data/providers/ifind_client.py", "runtime_role": "realtime, charts, snapshots, enrichment"},
            {"provider": "eastmoney", "path": "backend/features/market/charts.py", "runtime_role": "display chart fallback"},
            {"provider": "eastmoney", "path": "backend/data/realtime.py", "runtime_role": "isolated realtime observation"},
            {"provider": "tencent", "path": "backend/data/realtime.py", "runtime_role": "index observation fallback"},
        ],
        "provider_domains": [
            {"provider": "tushare", "path": "backend/data/providers/tushare_transport.py", "responsibility": "HTTP transport and provider errors"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_dashboard.py", "responsibility": "market overview and realtime breadth"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_indices.py", "responsibility": "market indices"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_industries.py", "responsibility": "Shenwan membership and industry snapshots"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_sectors.py", "responsibility": "generic sector snapshots"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_dragon_tiger.py", "responsibility": "hot-money directory and dragon-tiger activity"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_stocks.py", "responsibility": "stock detail and intraday bars"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_daily.py", "responsibility": "trading calendar, daily bars, and limit lists"},
            {"provider": "tushare", "path": "backend/data/providers/tushare_helpers.py", "responsibility": "shared deterministic normalization helpers"},
        ],
        "provider_construction": [
            {"client": "TushareClient", "owner": "backend/data/providers/tushare.py", "compatibility_fallback": "backend/features/market/service.py"},
            {"client": "DatahubClient", "owner": "backend/data/gateway.py"},
            {"client": "DatahubBridge", "owner": "backend/data/gateway.py"},
            {"client": "DatahubAwareTushareClient", "owner": "backend/data/gateway.py"},
            {"client": "IfindHttpClient", "owner": "backend/data/gateway.py"},
            {"client": "MarketChartClient", "owner": "backend/data/gateway.py"},
            {"client": "WebRealtimeAggregator", "owner": "backend/data/gateway.py"},
        ],
        "heaven_service_owners": {
            "facade": "backend/features/heaven/service.py",
            "manual_validation_and_safety": "backend/features/heaven/manual.py",
            "trend_orchestration_and_quality": "backend/features/heaven/trend.py",
            "market_context": "backend/features/heaven/market_context.py",
            "readings_and_interpretation": "backend/features/heaven/readings.py",
        },
        "market_insight_owners": {
            "facade": "backend/features/market/insights.py",
            "shared_context": "backend/features/market/insights_context.py",
            "auction_scoring": "backend/features/market/insights_auction_scoring.py",
            "auction_data": "backend/features/market/insights_auction_data.py",
            "auction_orchestration": "backend/features/market/insights_auction.py",
            "themes": "backend/features/market/insights_themes.py",
            "popularity": "backend/features/market/insights_popularity.py",
        },
        "application_owners": {
            "composition_root": "backend/application.py",
            "http_dispatch": "backend/http/dispatch.py",
            "system_service": "backend/features/system/service.py",
            "account_bridge": "backend/features/accounts/application.py",
            "job_lifecycle": "backend/jobs/service.py",
            "job_refresh_status": "backend/jobs/refresh.py",
            "feature_routes": "backend/features/*/routes.py",
        },
        "numeric_normalization": [
            {"function": "finite_number", "path": "backend/data/numbers.py"},
            {"function": "non_nan_number", "path": "backend/data/numbers.py"},
        ],
        "date_formatting": [
            {"function": "display_compact_date", "path": "backend/bootstrap/config.py"},
        ],
        "llm_entrypoints": [
            {"function": "stream_with_mentor", "path": "backend/features/mentor/agent.py"},
            {"function": "interpret_heaven", "path": "backend/features/heaven/agent.py"},
            {"function": "stream_review_assistant", "path": "backend/features/review/agent.py"},
            {"function": "compile_strategy_with_llm", "path": "backend/features/screener/compiler.py"},
            {"function": "test_llm_connection", "path": "backend/features/screener/compiler.py"},
        ],
        "llm_transport": [
            {"function": "chat_completion", "path": "backend/llm/transport.py"},
            {"function": "stream_chat_completion", "path": "backend/llm/transport.py"},
        ],
        "http_transport": [
            {"function": "send_json", "path": "backend/http/handler.py"},
            {"function": "send_ndjson_stream", "path": "backend/http/handler.py"},
        ],
        "css_layers": css_layers(html),
        "frontend_composition": {
            "shell": "frontend/index.html",
            "bootstrap": "frontend/bootstrap.js",
            "registry": "frontend/pages.config.js",
            "startup": "frontend/app.js",
            "runtime_owners": {
                "context": "frontend/shared/context.js",
                "application": "frontend/shared/application.js",
                "feedback": "frontend/shared/feedback.js",
                "dashboard": "frontend/shared/dashboard.js",
                "session": "frontend/shared/session.js",
                "admin": "frontend/shared/admin.js",
                "theme": "frontend/shared/theme.js",
                "table": "frontend/shared/table.js",
            },
            "market_runtime_owners": {
                "breadth": "frontend/pages/market/breadth.js",
                "charts": "frontend/pages/market/charts.js",
                "entity_detail": "frontend/pages/market/entity-detail.js",
                "stock_detail": "frontend/pages/market/stock-detail.js",
                "preview": "frontend/pages/market/preview.js",
                "search": "frontend/pages/market/search.js",
                "bindings": "frontend/pages/market/bindings.js",
            },
            "fragments": [path.split("?", 1)[0] for path in fragments],
        },
        "code_hotspots": code_hotspots(),
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Build the current architecture inventory")
    parser.add_argument("--check", action="store_true", help="fail when the committed inventory is stale")
    args = parser.parse_args()
    rendered = json.dumps(build(), ensure_ascii=False, indent=2) + "\n"
    if args.check:
        if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != rendered:
            raise SystemExit(
                "architecture inventory is stale; run "
                "tools/build_architecture_inventory.py"
            )
        print("Architecture inventory is current.")
        return 0
    OUTPUT.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT.write_text(rendered, encoding="utf-8")
    print(relative(OUTPUT))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
