#!/usr/bin/env python3
"""Build a SQL read projection from enriched structured 1C metadata."""

from __future__ import annotations

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


METADATA_FIELDS = (
    "attributes",
    "dimensions",
    "resources",
    "addressing_attributes",
    "accounting_flags",
)


STANDARD_COLUMNS = {
    "Document": ["_IDRRef", "_Marked", "_Date_Time", "_Number", "_Posted"],
    "Catalog": ["_IDRRef", "_Marked", "_PredefinedID", "_Description"],
    "AccumulationRegister": ["_Period", "_RecorderTRef", "_RecorderRRef", "_LineNo", "_Active", "_RecordKind"],
    "AccountingRegister": ["_Period", "_RecorderTRef", "_RecorderRRef", "_LineNo", "_Active"],
    # Information registers differ by periodicity/recorder settings. Standard
    # columns must be added from live table schema, not assumed globally.
    "InformationRegister": [],
    "BusinessProcess": ["_IDRRef", "_Marked", "_Date_Time", "_Number"],
    "Task": ["_IDRRef", "_Marked", "_Date_Time", "_Number"],
    "ChartOfAccounts": ["_IDRRef", "_Marked", "_PredefinedID", "_Code", "_Description"],
    "ChartOfCalculationTypes": ["_IDRRef", "_Marked", "_PredefinedID", "_Code", "_Description"],
}


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


def quote_ident(value: str) -> str:
    return "[" + value.replace("]", "]]") + "]"


def alias(value: str) -> str:
    return value.replace("]", "").replace("[", "").replace(".", "__")


def primary_tables(report: dict[str, Any]) -> list[str]:
    tables = []
    for route in report.get("object_storage_routes") or []:
        role = route.get("storage_role") or ""
        table = route.get("physical_name_candidate")
        if not table or route.get("route_kind") != "table":
            continue
        if role.endswith("ChngR") or role.endswith("SInf"):
            continue
        if role in {"BPrPoints", "AccumRgT", "AccumRgOpt", "AccRgAT0", "AccRgCT", "AccRgOpt"}:
            continue
        tables.append(table)
    return tables


def validation_column_map(validation: dict[str, Any]) -> dict[tuple[str, str], dict[str, Any]]:
    result = {}
    for row in validation.get("results") or []:
        if not row.get("found"):
            continue
        for match in row.get("matches") or []:
            result[(match["table"], match["column"])] = match
    return result


def item_columns(item: dict[str, Any], candidate_tables: list[str], schema: dict[tuple[str, str], dict[str, Any]]) -> list[dict[str, Any]]:
    result = []
    for column in item.get("physical_columns") or []:
        name = column.get("column")
        if not name:
            continue
        for table in candidate_tables:
            match = schema.get((table, name))
            result.append(
                {
                    "metadata_name": item.get("name"),
                    "metadata_uuid": item.get("uuid"),
                    "column": name,
                    "sql_type": match,
                    "value_type": item.get("value_type"),
                    "source": item.get("source"),
                    "extension_name": item.get("extension_name"),
                }
            )
    return result


def projected_items(items: list[dict[str, Any]], *, view: str, extension: str | None) -> list[dict[str, Any]]:
    if view == "effective":
        return items
    if view == "base":
        return [item for item in items if (item.get("source") or "base") != "extension"]
    if view == "extension":
        if not extension:
            raise SystemExit("Use --extension with --view extension.")
        result = []
        for item in items:
            if item.get("source") == "extension" and item.get("extension_name") == extension:
                result.append(item)
                continue
            for override in item.get("extension_overrides") or []:
                if override.get("extension_name") == extension:
                    copy = dict(item)
                    copy["source"] = "extension"
                    copy["extension_name"] = extension
                    if override.get("value_type"):
                        copy["value_type"] = override["value_type"]
                    result.append(copy)
                    break
        return result
    raise SystemExit(f"Unsupported view: {view}")


def select_sql(table: str, columns: list[dict[str, Any]], *, top: int) -> str:
    parts = []
    for index, col in enumerate(columns, start=1):
        name = col["column"]
        out_alias = col.get("alias") or f"c{index:03d}"
        col["select_alias"] = out_alias
        parts.append(f"  {quote_ident(name)} AS {quote_ident(out_alias)}")
    select_list = ",\n".join(parts) if parts else "  *"
    return f"SELECT TOP ({top})\n{select_list}\nFROM {quote_ident(table)};"


def build_projection(report: dict[str, Any], validation: dict[str, Any], *, top: int, view: str, extension: str | None) -> dict[str, Any]:
    schema = validation_column_map(validation)
    kind = report.get("kind")
    tables = primary_tables(report)
    main_table = tables[0] if tables else None
    diagnostics: dict[str, Any] = {"standard_columns_without_validation": []}

    main_columns = []
    if main_table:
        for column in STANDARD_COLUMNS.get(kind, []):
            match = schema.get((main_table, column))
            if not match:
                diagnostics["standard_columns_without_validation"].append({"scope": "main", "table": main_table, "column": column})
            main_columns.append(
                {
                    "metadata_name": f"standard.{column}",
                    "metadata_path": f"standard.{column}",
                    "column": column,
                    "sql_type": match,
                }
            )
        for field in METADATA_FIELDS:
            for item in projected_items(report.get(field) or [], view=view, extension=extension):
                for column in item_columns(item, [main_table], schema):
                    column["metadata_field"] = field
                    column["metadata_path"] = f"{field}.{item.get('name')}"
                    main_columns.append(column)

    table_parts = []
    by_parent: dict[str, list[dict[str, Any]]] = {}
    for item in projected_items(report.get("tabular_section_attributes") or [], view=view, extension=extension):
        parent_uuid = item.get("tabular_section_uuid") or item.get("parent_uuid")
        by_parent.setdefault(parent_uuid, []).append(item)

    for section in report.get("tabular_sections") or []:
        physical_tables = [row["table"] for row in section.get("physical_tables") or [] if row.get("table")]
        if not physical_tables:
            continue
        table = physical_tables[0]
        columns = []
        parent_id = f"{main_table}_IDRRef" if main_table else ""
        if parent_id:
            match = schema.get((table, parent_id))
            if not match:
                diagnostics["standard_columns_without_validation"].append({"scope": f"table_part:{section.get('name')}", "table": table, "column": parent_id})
            columns.append({"metadata_name": "standard.owner", "metadata_path": "standard.owner", "column": parent_id, "sql_type": match})
        for route in section.get("storage_routes") or []:
            if route.get("storage_role") == "LineNo":
                line_column = f"_LineNo{route['sql_number']}"
                match = schema.get((table, line_column))
                if not match:
                    diagnostics["standard_columns_without_validation"].append({"scope": f"table_part:{section.get('name')}", "table": table, "column": line_column})
                columns.append({"metadata_name": "standard.line_no", "metadata_path": "standard.line_no", "column": line_column, "sql_type": match})
        for item in by_parent.get(section.get("uuid"), []):
            for column in item_columns(item, [table], schema):
                column["metadata_field"] = "tabular_section_attributes"
                column["metadata_path"] = f"{section.get('name')}.{item.get('name')}"
                columns.append(column)
        table_parts.append(
            {
                "name": section.get("name"),
                "uuid": section.get("uuid"),
                "table": table,
                "columns": columns,
                "select_sql": select_sql(table, columns, top=top),
            }
        )

    return {
        "schema": "onec_sql_read_projection.v1",
        "kind": kind,
        "identity": report.get("identity"),
        "view": view,
        "extension": extension,
        "top": top,
        "main_table": main_table,
        "main_columns": main_columns,
        "main_select_sql": select_sql(main_table, main_columns, top=top) if main_table else None,
        "table_parts": table_parts,
        "diagnostics": diagnostics,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Build SQL read projection from enriched 1C metadata.")
    parser.add_argument("--metadata", type=Path, required=True)
    parser.add_argument("--validation", type=Path, required=True)
    parser.add_argument("--output", type=Path, required=True)
    parser.add_argument("--top", type=int, default=10)
    parser.add_argument("--view", choices=["effective", "base", "extension"], default="effective")
    parser.add_argument("--extension")
    args = parser.parse_args()

    projection = build_projection(load_json(args.metadata), load_json(args.validation), top=args.top, view=args.view, extension=args.extension)
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(projection, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"output": str(args.output), "main_table": projection.get("main_table"), "table_parts": len(projection.get("table_parts") or [])}, ensure_ascii=False))
    return 0


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