#!/usr/bin/env python3
"""Render 1C saved-state object report JSON as Markdown."""

from __future__ import annotations

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


def load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8-sig"))


def line(text: str = "") -> str:
    return text.rstrip() + "\n"


def bullet(text: str) -> str:
    return f"- {text}\n"


def code(value: Any) -> str:
    if value is None:
        return "`null`"
    return f"`{value}`"


def trim(value: str, max_chars: int) -> str:
    if len(value) <= max_chars:
        return value
    return value[: max_chars - 3].rstrip() + "..."


def storage_summary(storage: Any) -> str:
    rows = storage if isinstance(storage, list) else [storage]
    parts: list[str] = []
    for row in rows:
        if not isinstance(row, dict):
            continue
        saved = row.get("saved_table")
        active = row.get("active_table")
        name = row.get("file_name")
        active_exists = row.get("active_exists")
        parts.append(f"{saved}->{active} `{name}` active_exists=`{active_exists}`")
    return "; ".join(parts)


def render_object_change(change: dict[str, Any]) -> str:
    full_name = change.get("full_name") or change.get("name")
    layer = change.get("layer")
    kind_ru = change.get("kind_ru") or change.get("kind")
    synonym = change.get("synonym")
    out = bullet(f"{code(full_name)}: слой={code(layer)}, вид={code(kind_ru)}, состояние={code(change.get('change_state'))}")
    if synonym:
        out += f"  Синоним: {code(synonym)}\n"
    storage = storage_summary(change.get("storage") or [])
    if storage:
        out += f"  Хранилище: {storage}\n"
    return out


def render_system_change(change: dict[str, Any]) -> str:
    name = change.get("name")
    layer = change.get("layer")
    extension = change.get("extension")
    storage = storage_summary(change.get("storage") or {})
    suffix = f", расширение={code(extension)}" if extension else ""
    if storage:
        return bullet(f"{code(name)}: слой={code(layer)}{suffix}; {storage}")
    return bullet(f"{code(name)}: слой={code(layer)}{suffix}")


def render_agent_summary(summary: dict[str, Any]) -> str:
    out = ""
    objects = summary.get("object_changes") or []
    systems = summary.get("system_changes") or []
    if not objects and not systems:
        return out
    out += line()
    out += line("## Agent Summary")
    if summary.get("default_next_action"):
        out += bullet(f"Next action: {summary.get('default_next_action')}")
    for item in objects:
        parts = item.get("parts") or []
        added = item.get("added_terms") or item.get("added_words") or []
        removed = item.get("removed_terms") or item.get("removed_words") or []
        out += bullet(
            f"{code(item.get('full_name'))}: "
            f"parts={code(item.get('parts_count'))}, "
            f"text_diff_parts={code(item.get('text_diff_parts'))}, "
            f"active_missing_parts={code(item.get('active_missing_parts'))}"
        )
        if added:
            out += f"  Added terms: {', '.join(code(word) for word in added)}\n"
        if removed:
            out += f"  Removed terms: {', '.join(code(word) for word in removed)}\n"
        for part in parts:
            out += (
                "  Part: "
                f"{code(part.get('file_name'))}, "
                f"role={code(part.get('payload_role'))}, "
                f"active_exists={code(part.get('active_exists'))}, "
                f"summary={code(part.get('summary'))}, "
                f"delta_chars={code(part.get('delta_chars'))}\n"
            )
    if systems:
        names = ", ".join(code(item.get("name")) for item in systems)
        out += bullet(f"System changes: {names}")
    return out


def render_detail(detail: dict[str, Any], max_diff_lines: int, max_words: int) -> str:
    out = ""
    out += line(f"### {detail.get('full_name')}")
    out += line()
    out += bullet(f"Слой: {code(detail.get('layer'))}")
    if detail.get("extension"):
        out += bullet(f"Расширение: {code(detail.get('extension'))}")
    out += bullet(f"Вид объекта: {code(detail.get('kind_ru') or detail.get('kind'))}")
    out += bullet(f"Имя: {code(detail.get('name'))}")
    out += line()

    for part in detail.get("details") or []:
        payload = part.get("payload") or {}
        diff = payload.get("text_diff") or {}
        out += bullet(
            "Часть "
            f"{code(part.get('file_name'))}: "
            f"role={code(part.get('payload_role'))}, "
            f"{part.get('saved_table')}->{part.get('active_table')}, "
            f"active_exists={code(part.get('active_exists'))}, "
            f"text_comparable={code(payload.get('text_comparable'))}"
        )
        summary = payload.get("summary")
        if summary:
            out += f"  Итог: {summary}\n"
        if part.get("active_cas_key"):
            out += f"  Active CAS key: {code(part.get('active_cas_key'))}\n"
        if diff:
            out += (
                "  Размер текста: "
                f"active={code(diff.get('active_chars'))}, "
                f"saved={code(diff.get('saved_chars'))}, "
                f"delta={code(diff.get('delta_chars'))}\n"
            )
            added = diff.get("added_words") or []
            removed = diff.get("removed_words") or []
            hints = payload.get("semantic_hints") or {}
            added_terms = hints.get("added_terms") or []
            removed_terms = hints.get("removed_terms") or []
            if added_terms:
                out += f"  Добавленные термины: {', '.join(code(w) for w in added_terms[:max_words])}\n"
            if removed_terms:
                out += f"  Удаленные термины: {', '.join(code(w) for w in removed_terms[:max_words])}\n"
            if added:
                out += f"  Добавленные слова: {', '.join(code(w) for w in added[:max_words])}\n"
            if removed:
                out += f"  Удаленные слова: {', '.join(code(w) for w in removed[:max_words])}\n"
            diff_lines = diff.get("unified_diff") or []
            if diff_lines:
                out += "\n"
                out += "```diff\n"
                for diff_line in diff_lines[:max_diff_lines]:
                    out += trim(str(diff_line), 220) + "\n"
                if len(diff_lines) > max_diff_lines:
                    out += f"... truncated {len(diff_lines) - max_diff_lines} lines\n"
                out += "```\n"
                out += "\n"
    return out


def resolve_linked_path(report_path: Path, value: str | None) -> Path | None:
    if not value:
        return None
    path = Path(value)
    if path.is_absolute():
        return path
    return (report_path.parent / path).resolve()


def render(data: dict[str, Any], comparison: dict[str, Any], detail: dict[str, Any], max_diff_lines: int, max_words: int) -> str:
    counts = data.get("counts") or {}
    safety = data.get("safety") or {}
    out = ""
    out += line("# 1C Saved State Object Report")
    out += line()
    out += line(f"Database: `{data.get('database')}`")
    out += line(f"View: `{comparison.get('view')}`")
    out += line(f"Report: `{data.get('report')}`")
    out += line()
    out += line("## Summary")
    out += bullet(f"Object changes: `{counts.get('object_changes')}`")
    out += bullet(f"System changes: `{counts.get('system_changes')}`")
    out += bullet(f"Detail objects: `{counts.get('detail_objects')}`")
    out += bullet(f"Detail parts: `{counts.get('detail_parts')}`")
    out += line()
    out += line("## Safety")
    for key in ("read_only", "sql_write_performed", "public_terms_are_1c_objects", "secrets_in_report"):
        out += bullet(f"{key}: `{safety.get(key)}`")

    out += render_agent_summary(data.get("agent_summary") or {})

    object_changes = comparison.get("object_changes") or []
    if object_changes:
        out += line()
        out += line("## Object Changes")
        for change in object_changes:
            out += render_object_change(change)

    system_changes = comparison.get("system_changes") or []
    if system_changes:
        out += line()
        out += line("## System Changes")
        for change in system_changes:
            out += render_system_change(change)

    object_details = detail.get("object_details") or []
    if object_details:
        out += line()
        out += line("## Details")
        for item in object_details:
            out += render_detail(item, max_diff_lines=max_diff_lines, max_words=max_words)

    active_resolution = detail.get("active_extension_resolution") or {}
    if active_resolution:
        out += line()
        out += line("## Active Extension Resolution")
        out += bullet(f"Mapped parts: `{active_resolution.get('mapped_parts')}`")
        if active_resolution.get("extension_manifest_summary"):
            out += bullet(f"Manifest summary: `{active_resolution.get('extension_manifest_summary')}`")
        if active_resolution.get("config_cas_all_dir"):
            out += bullet(f"ConfigCAS all dir: `{active_resolution.get('config_cas_all_dir')}`")
    return out


def main() -> int:
    parser = argparse.ArgumentParser(description="Render 1C saved-state object report Markdown.")
    parser.add_argument("--report", type=Path, required=True)
    parser.add_argument("--comparison", type=Path)
    parser.add_argument("--detail", type=Path)
    parser.add_argument("--output", type=Path)
    parser.add_argument("--max-diff-lines", type=int, default=80)
    parser.add_argument("--max-words", type=int, default=20)
    args = parser.parse_args()

    report = load_json(args.report)
    comparison_path = args.comparison or resolve_linked_path(args.report, report.get("comparison"))
    detail_path = args.detail or resolve_linked_path(args.report, report.get("detail"))
    if comparison_path is None:
        raise SystemExit("Comparison path is required.")
    if detail_path is None:
        raise SystemExit("Detail path is required.")

    markdown = render(
        report,
        load_json(comparison_path),
        load_json(detail_path),
        max_diff_lines=args.max_diff_lines,
        max_words=args.max_words,
    )
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(markdown, encoding="utf-8")
        print(json.dumps({"output": str(args.output)}, ensure_ascii=False))
    else:
        print(markdown)
    return 0


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