#!/usr/bin/env python3
"""Build an agent-friendly 1C SQL read view from raw and resolved reports."""

from __future__ import annotations

import argparse
import json
import re
from collections import defaultdict
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 write_json(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def scope_key(scope: str, row_index: int, cell_key: str) -> tuple[str, int, str]:
    return (scope, row_index, cell_key)


def part_scope(part_name: str | None) -> str:
    return f"table_part:{part_name or ''}"


def best_alternate(alternates: list[dict[str, Any]]) -> dict[str, Any] | None:
    if not alternates:
        return None
    for alternate in alternates:
        route = alternate.get("route") or {}
        if alternate.get("found") and route.get("base_table"):
            return alternate
    for alternate in alternates:
        if alternate.get("found") and str(alternate.get("table") or "").endswith("X1"):
            return alternate
    for alternate in alternates:
        if alternate.get("found"):
            return alternate
    return alternates[0]


def presentation(values: dict[str, Any] | None) -> str | None:
    if not values:
        return None
    for key in ("_Description", "_Code", "_Number", "_EnumOrder"):
        value = values.get(key)
        if value is not None and value != "":
            return str(value)
    return None


BOOLEAN_STANDARD_PATHS = {"standard._Marked", "standard._Posted", "standard._Active"}


def normalize_1c_sql_date(value: Any) -> Any:
    if not isinstance(value, str):
        return value
    match = re.match(r"^(\d{4})(-\d{2}-\d{2}T.*)$", value)
    if not match:
        return value
    year = int(match.group(1))
    if year < 3000:
        return value
    return f"{year - 2000:04d}{match.group(2)}"


def field_has_boolean_type(field: dict[str, Any]) -> bool:
    if field.get("metadata_path") in BOOLEAN_STANDARD_PATHS:
        return True
    for column in (field.get("columns") or {}).values():
        value_type = column.get("value_type")
        values = value_type if isinstance(value_type, list) else [value_type]
        if any(item == "xs:boolean" for item in values):
            return True
    return False


def normalize_display_value(value: Any, field: dict[str, Any]) -> Any:
    if isinstance(value, dict) and value.get("kind") == "binary" and value.get("length") == 1 and field_has_boolean_type(field):
        hex_value = str(value.get("hex") or "").lower()
        if hex_value == "00":
            return False
        if hex_value == "01":
            return True
    return normalize_1c_sql_date(value)


def enum_order(values: dict[str, Any] | None) -> int | None:
    if not values:
        return None
    value = values.get("_EnumOrder")
    if value is None:
        return None
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def enum_presentation(item: dict[str, Any], enum_map: dict[str, Any] | None) -> dict[str, Any] | None:
    if not enum_map:
        return None
    target = item.get("target") or {}
    if target.get("kind") != "Enum":
        return None
    name = target.get("name")
    order = enum_order(item.get("values"))
    if name is None or order is None:
        return None
    enum = (enum_map.get("enums") or {}).get(name)
    if not enum:
        return None
    value = (enum.get("by_order") or {}).get(str(order))
    if not value:
        return None
    return value


def resolved_payload(item: dict[str, Any], *, mode: str, enum_map: dict[str, Any] | None = None) -> dict[str, Any]:
    enum_value = enum_presentation(item, enum_map)
    payload = {
        "mode": mode,
        "found": item.get("found"),
        "target": item.get("target"),
        "values": item.get("values"),
        "presentation": (enum_value or {}).get("synonym") or (enum_value or {}).get("name") or presentation(item.get("values")),
        "reason": item.get("reason"),
    }
    if enum_value:
        payload["enum_value"] = enum_value
    alternates = item.get("alternate_hits") or []
    if alternates:
        payload["alternate_hits"] = alternates
        selected = best_alternate(alternates)
        if selected:
            payload["selected_alternate"] = selected
            payload["selected_presentation"] = presentation(selected.get("values"))
    return payload


def simple_reference_index(reference_report: dict[str, Any] | None, enum_map: dict[str, Any] | None = None) -> dict[tuple[str, int, str], dict[str, Any]]:
    if not reference_report:
        return {}
    result = {}
    for item in reference_report.get("references") or []:
        ref = item.get("reference") or {}
        scope = "main" if ref.get("scope") == "main" else part_scope(ref.get("table_part_name"))
        row_index = int(ref.get("row_index") or 0)
        cell_key = ref.get("cell_key")
        if not cell_key:
            continue
        result[scope_key(scope, row_index, cell_key)] = resolved_payload(item, mode="single_reference", enum_map=enum_map)
    return result


def composite_reference_index(composite_report: dict[str, Any] | None, enum_map: dict[str, Any] | None = None) -> dict[tuple[str, int, str], dict[str, Any]]:
    if not composite_report:
        return {}
    result = {}
    for item in composite_report.get("composites") or []:
        comp = item.get("composite") or {}
        scope = "main" if comp.get("scope") == "main" else part_scope(comp.get("table_part_name"))
        row_index = int(comp.get("row_index") or 0)
        metadata_path = comp.get("metadata_path")
        if not metadata_path:
            continue
        payload = resolved_payload(item, mode="composite_reference", enum_map=enum_map)
        payload["type_hex"] = comp.get("type_hex")
        payload["rtref_hex"] = comp.get("rtref_hex")
        payload["rtref_sql_number"] = comp.get("rtref_sql_number")
        payload["rrref_hex"] = comp.get("rrref_hex")
        payload["columns"] = comp.get("columns")
        result[(scope, row_index, metadata_path)] = payload
    return result


def composite_value_index(composite_value_report: dict[str, Any] | None) -> dict[tuple[str, int, str], dict[str, Any]]:
    if not composite_value_report:
        return {}
    result = {}
    for item in composite_value_report.get("composites") or []:
        scope = "main" if item.get("scope") == "main" else part_scope(item.get("table_part_name"))
        row_index = int(item.get("row_index") or 0)
        metadata_path = item.get("metadata_path")
        if metadata_path:
            result[(scope, row_index, metadata_path)] = item
    return result


def display_value(field: dict[str, Any]) -> Any:
    resolved = field.get("resolved") or {}
    for key in ("presentation", "selected_presentation"):
        value = resolved.get(key)
        if value is not None:
            return value
    composite = field.get("composite_value") or {}
    selected = composite.get("selected") or {}
    if selected.get("branch") == "primitive":
        values = selected.get("primitive_values") or []
        if values:
            return normalize_display_value(values[0].get("value"), field)
    if field.get("value") is not None:
        return normalize_display_value(field.get("value"), field)
    columns = field.get("columns") or {}
    if len(columns) == 1:
        only = next(iter(columns.values()))
        return normalize_display_value(only.get("value"), field)
    return None


def enrich_row(
    row: dict[str, Any],
    *,
    scope: str,
    row_index: int,
    simple_refs: dict[tuple[str, int, str], dict[str, Any]],
    composite_refs: dict[tuple[str, int, str], dict[str, Any]],
    composite_values: dict[tuple[str, int, str], dict[str, Any]],
) -> dict[str, Any]:
    cells: dict[str, Any] = {}
    fields: dict[str, Any] = {}
    grouped: dict[str, list[tuple[str, dict[str, Any]]]] = defaultdict(list)

    for cell_key, cell in row.items():
        enriched = dict(cell)
        simple = simple_refs.get(scope_key(scope, row_index, cell_key))
        if simple:
            enriched["resolved"] = simple
        cells[cell_key] = enriched
        grouped[str(cell.get("metadata_path") or cell_key)].append((cell_key, enriched))

    for metadata_path, members in grouped.items():
        first = members[0][1]
        columns = {member["column"]: member for _, member in members if member.get("column")}
        field = {
            "metadata_path": metadata_path,
            "metadata_name": first.get("metadata_name"),
            "metadata_uuid": first.get("metadata_uuid"),
            "metadata_field": first.get("metadata_field"),
            "columns": columns,
        }
        composite = composite_refs.get((scope, row_index, metadata_path))
        composite_value = composite_values.get((scope, row_index, metadata_path))
        if composite:
            field["resolved"] = composite
        if composite_value:
            field["composite_value"] = composite_value
        if "resolved" not in field and len(members) == 1 and members[0][1].get("resolved"):
            field["resolved"] = members[0][1]["resolved"]
        elif "resolved" not in field and len(members) == 1:
            field["value"] = members[0][1].get("value")
        field["display_value"] = display_value(field)
        fields[metadata_path] = field

    return {"cells": cells, "fields": fields}


def build_view(
    read_result: dict[str, Any],
    reference_report: dict[str, Any] | None,
    composite_report: dict[str, Any] | None,
    composite_value_report: dict[str, Any] | None,
    enum_map: dict[str, Any] | None,
) -> dict[str, Any]:
    simple_refs = simple_reference_index(reference_report, enum_map)
    composite_refs = composite_reference_index(composite_report, enum_map)
    composite_values = composite_value_index(composite_value_report)

    main_rows = []
    for index, row in enumerate(read_result.get("main", {}).get("rows") or []):
        main_rows.append(
            {
                "row_index": index,
                **enrich_row(
                    row,
                    scope="main",
                    row_index=index,
                    simple_refs=simple_refs,
                    composite_refs=composite_refs,
                    composite_values=composite_values,
                ),
            }
        )

    table_parts = []
    for part in read_result.get("table_parts") or []:
        scope = part_scope(part.get("name"))
        rows = []
        for index, row in enumerate(part.get("rows") or []):
            rows.append(
                {
                    "row_index": index,
                    **enrich_row(
                        row,
                        scope=scope,
                        row_index=index,
                        simple_refs=simple_refs,
                        composite_refs=composite_refs,
                        composite_values=composite_values,
                    ),
                }
            )
        table_parts.append(
            {
                "name": part.get("name"),
                "uuid": part.get("uuid"),
                "table": part.get("table"),
                "row_count": part.get("row_count"),
                "rows": rows,
            }
        )

    return {
        "schema": "onec_sql_read_view.v1",
        "source_schema": read_result.get("schema"),
        "server": read_result.get("server"),
        "database": read_result.get("database"),
        "kind": read_result.get("kind"),
        "identity": read_result.get("identity"),
        "inputs": {
            "read_result": read_result.get("projection_path"),
            "reference_resolution_schema": (reference_report or {}).get("schema"),
            "composite_reference_resolution_schema": (composite_report or {}).get("schema"),
            "composite_value_resolution_schema": (composite_value_report or {}).get("schema"),
            "enum_presentation_map_schema": (enum_map or {}).get("schema"),
        },
        "summary": {
            "main_rows": len(main_rows),
            "table_parts": len(table_parts),
            "simple_reference_cells": len(simple_refs),
            "composite_reference_groups": len(composite_refs),
            "composite_value_groups": len(composite_values),
        },
        "main": {
            "table": read_result.get("main", {}).get("table"),
            "row_count": read_result.get("main", {}).get("row_count"),
            "rows": main_rows,
        },
        "table_parts": table_parts,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Build an enriched 1C SQL read view.")
    parser.add_argument("--read-result", type=Path, required=True)
    parser.add_argument("--reference-resolution", type=Path)
    parser.add_argument("--composite-reference-resolution", type=Path)
    parser.add_argument("--composite-value-resolution", type=Path)
    parser.add_argument("--enum-presentation-map", type=Path)
    parser.add_argument("--output", type=Path, required=True)
    args = parser.parse_args()

    read_result = load_json(args.read_result)
    reference_report = load_json(args.reference_resolution) if args.reference_resolution else None
    composite_report = load_json(args.composite_reference_resolution) if args.composite_reference_resolution else None
    composite_value_report = load_json(args.composite_value_resolution) if args.composite_value_resolution else None
    enum_map = load_json(args.enum_presentation_map) if args.enum_presentation_map else None
    view = build_view(read_result, reference_report, composite_report, composite_value_report, enum_map)
    view["inputs"]["read_result_path"] = str(args.read_result)
    if args.reference_resolution:
        view["inputs"]["reference_resolution_path"] = str(args.reference_resolution)
    if args.composite_reference_resolution:
        view["inputs"]["composite_reference_resolution_path"] = str(args.composite_reference_resolution)
    if args.composite_value_resolution:
        view["inputs"]["composite_value_resolution_path"] = str(args.composite_value_resolution)
    if args.enum_presentation_map:
        view["inputs"]["enum_presentation_map_path"] = str(args.enum_presentation_map)
    write_json(args.output, view)
    print(
        json.dumps(
            {
                "output": str(args.output),
                "main_rows": view["summary"]["main_rows"],
                "table_parts": view["summary"]["table_parts"],
                "simple_reference_cells": view["summary"]["simple_reference_cells"],
                "composite_reference_groups": view["summary"]["composite_reference_groups"],
                "composite_value_groups": view["summary"]["composite_value_groups"],
            },
            ensure_ascii=False,
        )
    )
    return 0


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