#!/usr/bin/env python3
"""Build a compact read-only evidence bundle for a 1C development task."""

from __future__ import annotations

import argparse
import base64
import json
import sys
from pathlib import Path
from typing import Any

sys.path.insert(0, str(Path(__file__).resolve().parent))
from get_1c_form_context import build_context as build_form_context  # noqa: E402
from get_1c_module import build_module_result  # noqa: E402
from get_1c_object_metadata import build_object_metadata  # noqa: E402
from plan_1c_task_context import build_plan  # noqa: E402
from resolve_1c_object import load_json  # noqa: E402


def decode_arg(value: str | None, encoded: str | None) -> str | None:
    if encoded:
        return base64.b64decode(encoded).decode("utf-8")
    return value


def collect_target_reads(investigation: dict[str, Any]) -> tuple[list[str], list[str]]:
    forms = []
    modules = []
    for read in investigation.get("recommended_reads") or []:
        form = read.get("form")
        module = read.get("module")
        if form and form not in forms:
            forms.append(form)
        if module and module not in modules:
            modules.append(module)
    for search in investigation.get("searches") or []:
        for match in search.get("matches") or []:
            form = match.get("form")
            if form and form not in forms:
                forms.append(form)
            area = match.get("area")
            module = match.get("name")
            if area in {"module", "module.code"} and module and module not in modules:
                modules.append(module)
    return forms, modules


def compact_metadata(metadata: dict[str, Any], *, max_attributes: int) -> dict[str, Any]:
    attrs = metadata.get("attributes") or []
    sections = metadata.get("tabular_sections") or []
    return {
        "schema": metadata.get("schema"),
        "view": metadata.get("view"),
        "object": metadata.get("object"),
        "attributes": attrs[:max_attributes],
        "attributes_total": len(attrs),
        "attributes_truncated": len(attrs) > max_attributes,
        "tabular_sections": sections,
        "counts": metadata.get("counts"),
    }


def compact_form_context(context: dict[str, Any], *, max_items: int, max_attributes: int, max_commands: int) -> dict[str, Any]:
    forms = []
    for form in context.get("forms") or []:
        copy = {key: form.get(key) for key in ("name", "synonym", "uuid", "origin", "effective_action", "meta_xml_path", "form_xml_path", "module_path", "extension_overlays") if form.get(key) not in (None, [], "")}
        structure = form.get("structure") or {}
        if structure:
            copy["structure"] = {
                "origin": structure.get("origin"),
                "form_xml_path": structure.get("form_xml_path"),
                "events": structure.get("events") or [],
                "items": (structure.get("items") or [])[:max_items],
                "attributes": (structure.get("attributes") or [])[:max_attributes],
                "commands": (structure.get("commands") or [])[:max_commands],
                "counts": structure.get("counts"),
            }
        overlays = []
        for overlay in form.get("extension_overlays") or []:
            overlay_copy = {key: overlay.get(key) for key in ("name", "synonym", "uuid", "origin", "effective_action", "meta_xml_path", "form_xml_path", "module_path") if overlay.get(key) not in (None, [], "")}
            structure = overlay.get("structure") or {}
            if structure:
                overlay_copy["structure"] = {
                    "origin": structure.get("origin"),
                    "form_xml_path": structure.get("form_xml_path"),
                    "events": structure.get("events") or [],
                    "items": (structure.get("items") or [])[:max_items],
                    "attributes": (structure.get("attributes") or [])[:max_attributes],
                    "commands": (structure.get("commands") or [])[:max_commands],
                    "counts": structure.get("counts"),
                }
            overlays.append(overlay_copy)
        if overlays:
            copy["extension_overlays"] = overlays
        forms.append(copy)
    return {
        "schema": context.get("schema"),
        "view": context.get("view"),
        "object": context.get("object"),
        "query": context.get("query"),
        "forms": forms,
        "counts": context.get("counts"),
    }


def compact_module_result(result: dict[str, Any]) -> dict[str, Any]:
    return {
        "schema": result.get("schema"),
        "view": result.get("view"),
        "object": result.get("object"),
        "query": result.get("query"),
        "modules": result.get("modules") or [],
        "counts": result.get("counts"),
    }


def read_lines(path: str) -> list[str]:
    file_path = Path(path)
    try:
        return file_path.read_text(encoding="utf-8-sig").splitlines()
    except UnicodeDecodeError:
        return file_path.read_text(encoding="cp1251", errors="replace").splitlines()


def code_snippet(path: str, line: int, *, radius: int, max_chars: int) -> dict[str, Any] | None:
    file_path = Path(path)
    if not file_path.is_file() or line <= 0:
        return None
    lines = read_lines(path)
    start = max(1, line - radius)
    end = min(len(lines), line + radius)
    text = "\n".join(lines[start - 1 : end])
    truncated = len(text) > max_chars
    return {
        "path": path,
        "line_start": start,
        "line_end": end,
        "focus_line": line,
        "text": text[:max_chars],
        "truncated": truncated,
        "char_count": len(text),
    }


def collect_code_snippets(searches: list[dict[str, Any]], *, radius: int, max_chars: int, limit: int) -> list[dict[str, Any]]:
    snippets = []
    seen = set()
    for search in searches:
        for match in search.get("matches") or []:
            if match.get("area") != "module.code":
                continue
            evidence = match.get("evidence") or {}
            path = evidence.get("path")
            line = evidence.get("line")
            if not path or not line:
                continue
            key = (path, line)
            if key in seen:
                continue
            seen.add(key)
            snippet = code_snippet(str(path), int(line), radius=radius, max_chars=max_chars)
            if not snippet:
                continue
            snippet.update(
                {
                    "search_text": search.get("text"),
                    "module": match.get("name"),
                    "origin": match.get("origin"),
                    "effective_action": match.get("effective_action"),
                }
            )
            snippets.append(snippet)
            if len(snippets) >= limit:
                return snippets
    return snippets


def evidence_for_investigation(
    index: dict[str, Any],
    investigation: dict[str, Any],
    *,
    view: str,
    max_attributes: int,
    max_form_items: int,
    max_form_attributes: int,
    max_form_commands: int,
    max_module_chars: int,
    code_snippet_radius: int,
    max_code_snippet_chars: int,
    max_code_snippets: int,
    max_forms: int,
    max_modules: int,
) -> dict[str, Any]:
    candidate = investigation.get("candidate") or {}
    kind = candidate.get("kind")
    name = candidate.get("name")
    metadata = build_object_metadata(index, kind=kind, name=name, view=view, extension=None, include_storage=False)
    forms, modules = collect_target_reads(investigation)
    form_contexts = []
    for form_name in forms[:max_forms]:
        form_contexts.append(
            compact_form_context(
                build_form_context(index, kind=kind, name=name, form=form_name, view=view, extension=None, max_items=max_form_items),
                max_items=max_form_items,
                max_attributes=max_form_attributes,
                max_commands=max_form_commands,
            )
        )
    module_contexts = []
    for module_name in modules[:max_modules]:
        module_contexts.append(
            compact_module_result(
                build_module_result(
                    index,
                    kind=kind,
                    name=name,
                    module_name=module_name,
                    view=view,
                    extension=None,
                    max_chars=max_module_chars,
                    routine=None,
                )
            )
        )
    snippets = collect_code_snippets(
        investigation.get("searches") or [],
        radius=code_snippet_radius,
        max_chars=max_code_snippet_chars,
        limit=max_code_snippets,
    )
    return {
        "candidate": candidate,
        "brief": investigation.get("brief"),
        "searches": investigation.get("searches") or [],
        "metadata": compact_metadata(metadata, max_attributes=max_attributes),
        "forms": form_contexts,
        "modules": module_contexts,
        "code_snippets": snippets,
        "recommended_reads": investigation.get("recommended_reads") or [],
        "counts": {
            "forms_materialized": len(form_contexts),
            "modules_materialized": len(module_contexts),
            "code_snippets": len(snippets),
        },
    }


def build_evidence(
    index: dict[str, Any],
    *,
    text: str,
    view: str,
    max_objects: int,
    max_terms: int,
    max_matches: int,
    max_attributes: int,
    max_form_items: int,
    max_form_attributes: int,
    max_form_commands: int,
    max_module_chars: int,
    code_snippet_radius: int,
    max_code_snippet_chars: int,
    max_code_snippets: int,
    max_forms: int,
    max_modules: int,
) -> dict[str, Any]:
    plan = build_plan(index, text=text, view=view, max_objects=max_objects, max_terms=max_terms, max_matches=max_matches)
    investigations = []
    for investigation in (plan.get("investigations") or [])[:max_objects]:
        investigations.append(
            evidence_for_investigation(
                index,
                investigation,
                view=view,
                max_attributes=max_attributes,
                max_form_items=max_form_items,
                max_form_attributes=max_form_attributes,
                max_form_commands=max_form_commands,
                max_module_chars=max_module_chars,
                code_snippet_radius=code_snippet_radius,
                max_code_snippet_chars=max_code_snippet_chars,
                max_code_snippets=max_code_snippets,
                max_forms=max_forms,
                max_modules=max_modules,
            )
        )
    return {
        "schema": "onec_task_evidence_bundle.v1",
        "view": view,
        "task": {"text": text},
        "plan": {
            "schema": plan.get("schema"),
            "object_candidates": plan.get("object_candidates") or [],
            "search_terms": plan.get("search_terms") or [],
            "safety": plan.get("safety"),
            "counts": plan.get("counts"),
        },
        "investigations": investigations,
        "limits": {
            "max_objects": max_objects,
            "max_terms": max_terms,
            "max_matches": max_matches,
            "max_attributes": max_attributes,
            "max_form_items": max_form_items,
            "max_form_attributes": max_form_attributes,
            "max_form_commands": max_form_commands,
            "max_module_chars": max_module_chars,
            "code_snippet_radius": code_snippet_radius,
            "max_code_snippet_chars": max_code_snippet_chars,
            "max_code_snippets": max_code_snippets,
            "max_forms": max_forms,
            "max_modules": max_modules,
        },
        "safety": {
            "mode": "read_only",
            "write_status": "blocked_until_write_gates",
            "write_contract": "docs/1c-write-path-safety.md",
        },
        "counts": {
            "investigations": len(investigations),
            "forms_materialized": sum(item.get("counts", {}).get("forms_materialized", 0) for item in investigations),
            "modules_materialized": sum(item.get("counts", {}).get("modules_materialized", 0) for item in investigations),
            "code_snippets": sum(item.get("counts", {}).get("code_snippets", 0) for item in investigations),
        },
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Build read-only 1C task evidence bundle.")
    parser.add_argument("--index", type=Path, required=True)
    parser.add_argument("--text")
    parser.add_argument("--text-b64")
    parser.add_argument("--view", choices=["effective", "base"], default="effective")
    parser.add_argument("--max-objects", type=int, default=2)
    parser.add_argument("--max-terms", type=int, default=8)
    parser.add_argument("--max-matches", type=int, default=8)
    parser.add_argument("--max-attributes", type=int, default=120)
    parser.add_argument("--max-form-items", type=int, default=250)
    parser.add_argument("--max-form-attributes", type=int, default=120)
    parser.add_argument("--max-form-commands", type=int, default=80)
    parser.add_argument("--max-module-chars", type=int, default=12000)
    parser.add_argument("--code-snippet-radius", type=int, default=8)
    parser.add_argument("--max-code-snippet-chars", type=int, default=8000)
    parser.add_argument("--max-code-snippets", type=int, default=20)
    parser.add_argument("--max-forms", type=int, default=3)
    parser.add_argument("--max-modules", type=int, default=4)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()

    text = decode_arg(args.text, args.text_b64)
    if not text:
        raise SystemExit("Use --text or --text-b64.")
    result = build_evidence(
        load_json(args.index),
        text=text,
        view=args.view,
        max_objects=args.max_objects,
        max_terms=args.max_terms,
        max_matches=args.max_matches,
        max_attributes=args.max_attributes,
        max_form_items=args.max_form_items,
        max_form_attributes=args.max_form_attributes,
        max_form_commands=args.max_form_commands,
        max_module_chars=args.max_module_chars,
        code_snippet_radius=args.code_snippet_radius,
        max_code_snippet_chars=args.max_code_snippet_chars,
        max_code_snippets=args.max_code_snippets,
        max_forms=args.max_forms,
        max_modules=args.max_modules,
    )
    output = json.dumps(result, ensure_ascii=False, indent=2) + "\n"
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(output, encoding="utf-8")
        print(json.dumps({"output": str(args.output), "counts": result["counts"], "view": result["view"]}, ensure_ascii=False))
    else:
        print(output)
    return 0


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