from __future__ import annotations

import argparse
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Any
from urllib import request


DEFAULT_KINDS = [
    "Catalog",
    "Document",
    "ChartOfCharacteristicTypes",
    "ChartOfAccounts",
    "ChartOfCalculationTypes",
    "ExchangePlan",
    "Report",
    "DataProcessor",
]


def rpc(adapter_url: str, method: str, payload: dict[str, Any]) -> dict[str, Any]:
    body = json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8")
    req = request.Request(
        f"{adapter_url.rstrip('/')}/rpc",
        data=body,
        headers={"Content-Type": "application/json; charset=utf-8"},
        method="POST",
    )
    with request.urlopen(req, timeout=240) as resp:
        return json.loads(resp.read().decode("utf-8", errors="replace"))


def list_objects_for_kind(adapter_url: str, base_id: str, kind: str, page_size: int, max_objects: int | None) -> list[dict[str, Any]]:
    items: list[dict[str, Any]] = []
    offset = 0
    while True:
        result = rpc(
            adapter_url,
            "metadata.objects.list",
            {
                "base_id": base_id,
                "kind": kind,
                "limit": page_size,
                "offset": offset,
            },
        )
        rows = result.get("objects") or []
        if not rows:
            break
        items.extend(rows)
        if max_objects is not None and len(items) >= max_objects:
            return items[:max_objects]
        if len(rows) < page_size:
            break
        offset += page_size
    return items


def read_templates_for_object(adapter_url: str, base_id: str, obj: dict[str, Any], timeout_seconds: int) -> dict[str, Any]:
    payload = {
        "base_id": base_id,
        "guid": obj.get("guid"),
        "kind": obj.get("kind"),
        "name": obj.get("name"),
        "timeout_seconds": timeout_seconds,
        "include_storage": False,
    }
    result = rpc(adapter_url, "metadata.object.templates", payload)
    templates = result.get("templates") or []
    if not templates:
        return {}
    return {
        "object": {
            "guid": obj.get("guid"),
            "kind": obj.get("kind"),
            "kind_ru": obj.get("kind_ru"),
            "name": obj.get("name"),
            "synonym": obj.get("synonym"),
            "source": obj.get("source"),
        },
        "counts": result.get("counts") or {},
        "templates": templates,
    }


def is_tabular_template(template: dict[str, Any]) -> bool:
    features = template.get("features") if isinstance(template.get("features"), dict) else {}
    if features.get("tabular_document") is True:
        return True
    return str(template.get("format") or "") == "ТабличныйДокумент"


def render_markdown(payload: dict[str, Any]) -> str:
    lines: list[str] = []
    lines.append("# 1C configuration template inventory")
    lines.append("")
    lines.append(f"- Base: `{payload.get('base_id')}`")
    lines.append(f"- Kinds: `{', '.join(payload.get('kinds') or [])}`")
    lines.append(f"- Scanned objects: `{payload.get('counts', {}).get('objects_scanned')}`")
    lines.append(f"- Objects with templates: `{payload.get('counts', {}).get('objects_with_templates')}`")
    lines.append(f"- Templates total: `{payload.get('counts', {}).get('templates_total')}`")
    lines.append("")
    lines.append("| Object | Kind | Templates | Template names |")
    lines.append("| --- | --- | --- | --- |")
    for item in payload.get("items") or []:
        obj = item.get("object") or {}
        names = ", ".join(str(template.get("name") or "") for template in (item.get("templates") or []))
        lines.append(
            f"| `{obj.get('name')}` | `{obj.get('kind')}` | `{len(item.get('templates') or [])}` | `{names}` |"
        )
    return "\n".join(lines) + "\n"


def main() -> int:
    parser = argparse.ArgumentParser(description="Inventory templates across live 1C configuration objects.")
    parser.add_argument("--adapter-url", default="http://docker.cin.su:8011")
    parser.add_argument("--base-id", default="upo_test")
    parser.add_argument("--kind", action="append", dest="kinds", help="Repeatable metadata kind filter.")
    parser.add_argument("--page-size", type=int, default=200)
    parser.add_argument("--max-objects-per-kind", type=int)
    parser.add_argument("--workers", type=int, default=6)
    parser.add_argument("--timeout-seconds", type=int, default=60)
    parser.add_argument("--only-tabular", action="store_true", help="Keep only templates of type 'Табличный документ'.")
    parser.add_argument("--base-only", action="store_true", help="Keep only base configuration objects, skip extension objects.")
    parser.add_argument(
        "--output-json",
        default=str(Path("Z:/codex/LLM/reports/1c-template-baselines/upo_test_configuration_templates.json")),
    )
    parser.add_argument(
        "--output-markdown",
        default=str(Path("Z:/codex/LLM/reports/1c-template-baselines/upo_test_configuration_templates.md")),
    )
    args = parser.parse_args()

    kinds = args.kinds or list(DEFAULT_KINDS)
    objects: list[dict[str, Any]] = []
    for kind in kinds:
        objects.extend(list_objects_for_kind(args.adapter_url, args.base_id, kind, args.page_size, args.max_objects_per_kind))
    if args.base_only:
        objects = [obj for obj in objects if str(obj.get("source") or "") == "base"]

    found: list[dict[str, Any]] = []
    with ThreadPoolExecutor(max_workers=max(1, int(args.workers or 1))) as pool:
        future_map = {
            pool.submit(read_templates_for_object, args.adapter_url, args.base_id, obj, int(args.timeout_seconds or 60)): obj
            for obj in objects
        }
        for future in as_completed(future_map):
            result = future.result()
            if result:
                if args.only_tabular:
                    result["templates"] = [item for item in (result.get("templates") or []) if is_tabular_template(item)]
                    if not result["templates"]:
                        continue
                found.append(result)

    found.sort(key=lambda item: ((item.get("object") or {}).get("kind") or "", (item.get("object") or {}).get("name") or ""))
    payload = {
        "schema": "codex_1c_configuration_templates_inventory.v1",
        "adapter_url": args.adapter_url,
        "base_id": args.base_id,
        "kinds": kinds,
        "items": found,
        "counts": {
            "objects_scanned": len(objects),
            "objects_with_templates": len(found),
            "templates_total": sum(len(item.get("templates") or []) for item in found),
        },
    }

    json_path = Path(args.output_json)
    md_path = Path(args.output_markdown)
    json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    md_path.write_text(render_markdown(payload), encoding="utf-8")
    print(
        json.dumps(
            {
                "status": "ok",
                "json": str(json_path),
                "markdown": str(md_path),
                "counts": payload["counts"],
            },
            ensure_ascii=False,
            indent=2,
        )
    )
    return 0


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