from __future__ import annotations

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


ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "plugins" / "1c"))
sys.path.insert(0, str(ROOT / "plugins" / "1c" / "connector"))

import adapter_1c_server as adapter_server  # noqa: E402
from parser.payload import compress_payload  # noqa: E402


def require(condition: bool, message: str, failures: list[str]) -> None:
    if not condition:
        failures.append(message)


def run_override(action_evidence: dict[str, Any] | None) -> dict[str, Any]:
    adapter_server.metadata_object_modules = lambda payload: {
        "status": "ok",
        "object": {"kind": "Catalog", "name": "Номенклатура"},
        "modules": [{"module_id": "ConfigCAS:ext-guid__module-guid.0", "name": "object"}],
    }
    selection = {
        "routine_name": "ПередЗаписью",
        "line_start": 1,
        "line_end": 3,
        "match_by": "routine_exact",
    }
    if action_evidence:
        selection.update(action_evidence)
    adapter_server.read_module = lambda payload: {"status": "ok", "selection": selection}
    return adapter_server.call_method(
        "metadata.resolve_overrides",
        {
            "base_id": "upo_test",
            "object_type": "Catalog",
            "object_name": "Номенклатура",
            "method_name": "ПередЗаписью",
        },
    )


def run_base_override() -> dict[str, Any]:
    adapter_server.metadata_object_modules = lambda payload: {
        "status": "ok",
        "object": {"kind": "Catalog", "name": "Номенклатура"},
        "modules": [{"module_id": "Config:object-module.0", "name": "object"}],
    }
    adapter_server.read_module = lambda payload: {
        "status": "ok",
        "selection": {
            "routine_name": "ПередЗаписью",
            "line_start": 1,
            "line_end": 3,
            "match_by": "routine_exact",
        },
    }
    return adapter_server.call_method(
        "metadata.resolve_overrides",
        {
            "base_id": "upo_test",
            "object_type": "Catalog",
            "object_name": "Номенклатура",
            "method_name": "ПередЗаписью",
        },
    )


def run_saved_state_search_with_object_name(params: dict[str, Any] | None = None) -> dict[str, Any]:
    module_text = "Процедура ПередЗаписью(Отказ)\nКонецПроцедуры".encode("utf-8")
    header = f"\r\n{len(module_text):08x} {len(module_text):08x} 7fffffff \r\n".encode("ascii")
    stored = compress_payload(b"prefix" + header + module_text, "raw_deflate")
    adapter_server.metadata_cache_lookup_row = lambda base_id, kind, name: {
        "guid": "owner-guid",
        "kind": kind,
        "kind_ru": "Справочник",
        "public_kind": "catalog",
        "name": name,
        "source": "base",
    }
    adapter_server.storage_files_list = lambda payload: {
        "status": "ok",
        "files": [{"FileName": "owner-guid__module-guid.0", "PartCount": 1, "Bytes": len(stored)}],
    }
    adapter_server.read_storage_file_bytes = lambda base_id, table, file_name, timeout_seconds=30: (stored, {"database": base_id}, None)
    request_payload = {
        "base_id": "upo_test",
        "layer": "extension_saved_state",
        "object_type": "Catalog",
        "object_name": "Номенклатура",
        "query": "ПередЗаписью",
        "limit": 10,
    }
    request_payload.update(params or {})
    request_payload["include_storage"] = True
    return adapter_server.call_method(
        adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD,
        request_payload,
    )


def first_action(result: dict[str, Any]) -> dict[str, Any]:
    chain = result.get("chain") if isinstance(result.get("chain"), list) else []
    first = chain[0] if chain and isinstance(chain[0], dict) else {}
    action = first.get("extension_action") if isinstance(first.get("extension_action"), dict) else {}
    return action


def run_checks() -> dict[str, Any]:
    failures: list[str] = []

    unknown = run_override(None)
    unknown_action = first_action(unknown)
    unknown_evidence = unknown.get("write_plan_evidence") if isinstance(unknown.get("write_plan_evidence"), dict) else {}
    unknown_next = unknown_evidence.get("next_resolution") if isinstance(unknown_evidence.get("next_resolution"), dict) else {}
    unknown_next_params = unknown_next.get("params") if isinstance(unknown_next.get("params"), dict) else {}
    require(unknown.get("status") == "ok", "override chain with extension routine must resolve", failures)
    require(unknown_action.get("status") == "unknown", "extension routine without action evidence must be unknown", failures)
    require(unknown_action.get("operation_class") == "unknown_extension_action", "unknown action must not become replace", failures)
    require((unknown_evidence.get("target") or {}).get("extension_action") == unknown_action, "unknown action must be carried into write_plan_evidence target", failures)
    require("intent" not in unknown_evidence, "unknown action must not infer write intent", failures)
    require(unknown_next.get("method") == adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD, "write_plan_evidence must expose saved-state module resolver", failures)
    require(unknown_next_params.get("layer") == "extension_saved_state", "extension write_plan_evidence must use extension_saved_state", failures)
    require(unknown_next_params.get("ref") == "Catalog.Номенклатура", "extension write_plan_evidence must use a public object ref", failures)
    require("tables" not in unknown_next_params and "owner_guid" not in unknown_next_params, "public extension evidence must hide storage selectors", failures)

    controlled = run_override({"operation_class": "replace_with_control"})
    controlled_action = first_action(controlled)
    controlled_evidence = controlled.get("write_plan_evidence") if isinstance(controlled.get("write_plan_evidence"), dict) else {}
    controlled_next = controlled_evidence.get("next_resolution") if isinstance(controlled_evidence.get("next_resolution"), dict) else {}
    controlled_next_params = controlled_next.get("params") if isinstance(controlled_next.get("params"), dict) else {}
    require(controlled_action.get("status") == "ok", "known extension action must be ok", failures)
    require(controlled_action.get("operation_class") == "replace_with_control", "replace_with_control must be preserved", failures)
    require(controlled_action.get("requires_control_fragment") is True, "replace_with_control must require control fragment", failures)
    require((controlled_evidence.get("target") or {}).get("extension_action") == controlled_action, "known action must be carried into write_plan_evidence target", failures)
    require((controlled_evidence.get("intent") or {}).get("operation") == "replace_with_control", "known action must infer write_plan_evidence intent", failures)
    require(controlled_next.get("method") == adapter_server.SAVED_STATE_MODULES_SEARCH_METHOD, "known action evidence must expose saved-state module resolver", failures)
    require(controlled_next_params.get("query") == "ПередЗаписью", "known action resolver params must carry routine query", failures)

    before = run_override({"action": "вставить до"})
    before_action = first_action(before)
    require(before_action.get("operation_class") == "insert_before", "Russian insert-before action must normalize", failures)

    base = run_base_override()
    base_action = first_action(base)
    base_evidence = base.get("write_plan_evidence") if isinstance(base.get("write_plan_evidence"), dict) else {}
    base_next = base_evidence.get("next_resolution") if isinstance(base_evidence.get("next_resolution"), dict) else {}
    base_next_params = base_next.get("params") if isinstance(base_next.get("params"), dict) else {}
    require(base_action.get("operation_class") == "base_definition", "base routine must be marked as base_definition", failures)
    require(base_action.get("requires_control_fragment") is False, "base routine must not require control fragment", failures)
    require(base_next_params.get("layer") == "base_saved_state", "base write_plan_evidence must use base_saved_state", failures)
    require(base_next_params.get("ref") == "Catalog.Номенклатура", "base write_plan_evidence must use a public object ref", failures)

    saved_state = run_saved_state_search_with_object_name(controlled_next_params)
    saved_state_owner = saved_state.get("owner_resolution") if isinstance(saved_state.get("owner_resolution"), dict) else {}
    saved_state_modules = saved_state.get("modules") if isinstance(saved_state.get("modules"), list) else []
    saved_state_streams = saved_state_modules[0].get("streams") if saved_state_modules and isinstance(saved_state_modules[0], dict) and isinstance(saved_state_modules[0].get("streams"), list) else []
    saved_state_target = saved_state_streams[0].get("write_plan_target") if saved_state_streams and isinstance(saved_state_streams[0], dict) and isinstance(saved_state_streams[0].get("write_plan_target"), dict) else {}
    require(saved_state.get("status") == "ok", "saved-state module search with object name must be accepted", failures)
    require(saved_state_owner.get("owner_guid") == "owner-guid", "saved-state module search must resolve object name to owner_guid", failures)
    require(bool(saved_state_modules), "saved-state module search with resolved owner must find module", failures)
    require(saved_state_target.get("module_ref") == "ConfigCASSave:owner-guid__module-guid.0#stream:0", "saved-state stream must expose write_plan_target.module_ref", failures)
    require(saved_state_target.get("expected_sha1"), "saved-state stream must expose write_plan_target.expected_sha1", failures)

    concrete_plan_target = {
        **(controlled_evidence.get("target") if isinstance(controlled_evidence.get("target"), dict) else {}),
        **saved_state_target,
    }
    concrete_plan_intent = {
        **(controlled_evidence.get("intent") if isinstance(controlled_evidence.get("intent"), dict) else {}),
        "control_fragment": "Процедура ПередЗаписью",
        "new": "Процедура ПередЗаписью(Отказ)\n\t// smoke\nКонецПроцедуры",
    }
    concrete_plan = adapter_server.call_method(
        adapter_server.METADATA_WRITE_PLAN_METHOD,
        {
            "base_id": "upo_test",
            "target": concrete_plan_target,
            "intent": concrete_plan_intent,
            "resolve_origin": False,
        },
    )
    concrete_route = concrete_plan.get("route") if isinstance(concrete_plan.get("route"), dict) else {}
    concrete_hint = concrete_route.get("apply_payload_hint") if isinstance(concrete_route.get("apply_payload_hint"), dict) else {}
    concrete_hint_payload = concrete_hint.get("payload") if isinstance(concrete_hint.get("payload"), dict) else {}
    require(concrete_plan.get("allowed") is True, "write_plan_evidence plus saved-state write_plan_target must produce an allowed concrete plan", failures)
    require(concrete_route.get("apply_method") == adapter_server.MODULE_WRITE_APPLY_METHOD, "concrete override write plan must route to module write apply", failures)
    require(concrete_route.get("operation_class") == "replace_with_control", "concrete override write plan must preserve extension operation class", failures)
    require(concrete_hint.get("ready_for_apply_method") is True, "concrete override write plan hint must be ready for apply method", failures)
    require(concrete_hint_payload.get("module_ref") == saved_state_target.get("module_ref"), "concrete override write plan hint must carry module_ref", failures)
    require(concrete_hint_payload.get("expected_sha1") == saved_state_target.get("expected_sha1"), "concrete override write plan hint must carry expected_sha1", failures)

    return {
        "schema": "onec_extension_action_contract_check.v1",
        "status": "ok" if not failures else "failed",
        "failures": failures,
        "checks": {
            "unknown_extension_action": "extension routine without action evidence stays unknown",
            "replace_with_control": "controlled replacement action requires control fragment",
            "russian_insert_before": "Russian action names normalize to stable classes",
            "base_definition": "base routines are not treated as extension actions",
            "write_plan_evidence": "override results include a ready metadata.write.plan evidence fragment",
            "write_plan_next_resolution": "write_plan_evidence points to the saved-state module resolver",
            "saved_state_module_name_selector": "saved-state module search resolves object_type/object_name to owner_guid",
            "saved_state_stream_write_plan_target": "saved-state module streams expose concrete metadata.write.plan target",
            "override_to_concrete_write_plan": "write_plan_evidence and write_plan_target compose into an allowed concrete module plan",
        },
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Check extension routine action contract invariants.")
    parser.add_argument("--print", action="store_true")
    args = parser.parse_args()
    report = run_checks()
    if args.print or report["status"] != "ok":
        print(json.dumps(report, ensure_ascii=False, indent=2))
    else:
        print("1C extension action contract status: ok")
    return 0 if report["status"] == "ok" else 1


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