from __future__ import annotations

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

import yaml


ROOT = Path(__file__).resolve().parents[1]
CONNECTOR = ROOT / "plugins" / "1c" / "connector"
PARSER = ROOT / "plugins" / "1c" / "parser"


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


def read_yaml(path: Path) -> dict[str, Any]:
    with path.open("r", encoding="utf-8") as handle:
        data = yaml.safe_load(handle)
    return data if isinstance(data, dict) else {}


def read_toml(path: Path) -> dict[str, Any]:
    with path.open("rb") as handle:
        data = tomllib.load(handle)
    return data if isinstance(data, dict) else {}


def run_checks() -> dict[str, Any]:
    failures: list[str] = []
    required = [
        CONNECTOR / "adapter_1c_server.py",
        CONNECTOR / "contracts" / "openapi.yaml",
        CONNECTOR / "policies" / "read-only-query.yaml",
        CONNECTOR / "policies" / "sql-base-access-policy.yaml",
        CONNECTOR / "policies" / "change-workflow.yaml",
        CONNECTOR / "policies" / "config-layer-write-policy.yaml",
        CONNECTOR / "Dockerfile",
        CONNECTOR / "docker-compose.yml",
        CONNECTOR / ".env.example",
        CONNECTOR / "pyproject.toml",
        CONNECTOR / "service.yaml",
        CONNECTOR / "README.md",
        PARSER / "__init__.py",
        PARSER / "payload.py",
        PARSER / "cas_payload.py",
        PARSER / "common_command.py",
        PARSER / "scheduled_job.py",
    ]
    missing = [str(path.relative_to(ROOT)) for path in required if not path.exists()]
    require(not missing, f"missing standalone connector files: {missing}", failures)

    service = read_yaml(CONNECTOR / "service.yaml") if (CONNECTOR / "service.yaml").exists() else {}
    compose = read_yaml(CONNECTOR / "docker-compose.yml") if (CONNECTOR / "docker-compose.yml").exists() else {}
    pyproject = read_toml(CONNECTOR / "pyproject.toml") if (CONNECTOR / "pyproject.toml").exists() else {}
    openapi = read_yaml(CONNECTOR / "contracts" / "openapi.yaml") if (CONNECTOR / "contracts" / "openapi.yaml").exists() else {}
    access_policy = read_yaml(CONNECTOR / "policies" / "sql-base-access-policy.yaml") if (CONNECTOR / "policies" / "sql-base-access-policy.yaml").exists() else {}

    require(service.get("id") == "onec-adapter-connector", "service.yaml must identify onec-adapter-connector", failures)
    require(service.get("status") == "standalone-ready", "service.yaml status must be standalone-ready", failures)
    require((service.get("runtime") or {}).get("entrypoint") == "adapter_1c_server.py", "service entrypoint must be adapter_1c_server.py", failures)
    require("contracts/openapi.yaml" == (service.get("contracts") or {}).get("openapi"), "service must point to connector OpenAPI contract", failures)
    registered_policies = (service.get("contracts") or {}).get("policies") or []
    require("policies/sql-base-access-policy.yaml" in registered_policies, "service must register SQL base access policy", failures)

    base_settings = access_policy.get("base_settings") if isinstance(access_policy.get("base_settings"), dict) else {}
    read_scope = access_policy.get("read_scope") if isinstance(access_policy.get("read_scope"), dict) else {}
    write_scope = access_policy.get("write_scope") if isinstance(access_policy.get("write_scope"), dict) else {}
    identity = access_policy.get("sql_identity_management") if isinstance(access_policy.get("sql_identity_management"), dict) else {}
    require(access_policy.get("status") == "active", "SQL base access policy must be active", failures)
    require(base_settings.get("selector") == "base_id", "SQL settings must be selected by base_id", failures)
    require(set(base_settings.get("required_fields") or []) == {"server", "database", "user"}, "SQL base settings must require server, database, and user", failures)
    require(read_scope.get("application_data") == "read_only", "application data must be read-only", failures)
    require(read_scope.get("metadata_structure") == "read_only", "metadata structure must be readable without mutation", failures)
    require(set((write_scope.get("allowed") or {}).values()) == {"ConfigSave", "ConfigCASSave"}, "only ConfigSave and ConfigCASSave may be write targets", failures)
    require(identity.get("mode") == "forbidden", "SQL identity management must be forbidden", failures)

    project = pyproject.get("project") if isinstance(pyproject.get("project"), dict) else {}
    require(project.get("name") == "onec-adapter-connector", "pyproject project.name must be onec-adapter-connector", failures)
    scripts = project.get("scripts") if isinstance(project.get("scripts"), dict) else {}
    require(scripts.get("onec-adapter") == "adapter_1c_server:main", "pyproject must expose onec-adapter script", failures)
    dependencies = project.get("dependencies") if isinstance(project.get("dependencies"), list) else []
    require(any(str(dep).startswith("pymssql") for dep in dependencies), "pyproject must include pymssql dependency", failures)

    services = compose.get("services") if isinstance(compose.get("services"), dict) else {}
    adapter_service = services.get("onec-adapter") if isinstance(services.get("onec-adapter"), dict) else {}
    build = adapter_service.get("build") if isinstance(adapter_service.get("build"), dict) else {}
    require(build.get("context") == "..", "docker-compose build context must include parser sibling", failures)
    require(build.get("dockerfile") == "connector/Dockerfile", "docker-compose must use connector/Dockerfile", failures)
    require(bool(adapter_service.get("healthcheck")), "docker-compose must define a healthcheck", failures)

    require(openapi.get("openapi") == "3.1.0", "connector OpenAPI must parse as 3.1.0", failures)
    paths = openapi.get("paths") if isinstance(openapi.get("paths"), dict) else {}
    require("/health" in paths, "connector OpenAPI must expose /health", failures)
    require("/methods" in paths, "connector OpenAPI must expose runtime /methods registry", failures)
    require("/rpc" in paths, "connector OpenAPI must expose universal /rpc", failures)
    require("/metadata/write-plan" in paths, "connector OpenAPI must expose /metadata/write-plan", failures)
    schemas = ((openapi.get("components") or {}).get("schemas") or {}) if isinstance(openapi.get("components"), dict) else {}
    require("AdapterRpcRequest" in schemas, "connector OpenAPI must define AdapterRpcRequest", failures)
    require("AdapterMethodsResponse" in schemas, "connector OpenAPI must define AdapterMethodsResponse", failures)
    try:
        sys.path.insert(0, str(CONNECTOR))
        sys.path.insert(0, str(CONNECTOR.parent))
        import adapter_1c_server as adapter_server

        runtime_paths = set(adapter_server.HTTP_GET_METHOD_ROUTES) | set(adapter_server.HTTP_POST_METHOD_ROUTES) | {"/rpc"}
        require(
            set(paths) == runtime_paths,
            f"OpenAPI/runtime HTTP route drift: missing_in_openapi={sorted(runtime_paths - set(paths))}, missing_in_runtime={sorted(set(paths) - runtime_paths)}",
            failures,
        )
        require(
            all("get" in (paths.get(path) or {}) for path in adapter_server.HTTP_GET_METHOD_ROUTES),
            "every runtime GET route must be declared as GET in OpenAPI",
            failures,
        )
        require(
            all("post" in (paths.get(path) or {}) for path in adapter_server.HTTP_POST_METHOD_ROUTES),
            "every runtime POST route must be declared as POST in OpenAPI",
            failures,
        )
    except Exception as exc:
        failures.append(f"could not verify runtime HTTP route registry: {exc}")

    return {
        "schema": "onec_connector_standalone_check.v1",
        "status": "ok" if not failures else "failed",
        "failures": failures,
        "checks": {
            "required_files": not missing,
            "service_manifest": service.get("id"),
            "pyproject": project.get("name"),
            "compose_service": "onec-adapter" in services,
            "openapi": openapi.get("openapi"),
        },
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Check 1C connector standalone-ready service packaging.")
    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 connector standalone status: ok")
    return 0 if report["status"] == "ok" else 1


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