from __future__ import annotations

from typing import Any

from ..backend_client import BackendClient, BackendRequestContext
from ..json_types import JSONObject, JSONValue

WORKFLOW_SPEC_SCHEMA_PATH = "/api/workflow/spec/schema"
WORKFLOW_SPEC_GET_PATH = "/api/workflow/spec"
WORKFLOW_SPEC_APPLY_PATH = "/api/workflow/spec:apply"
DEFAULT_SCHEMA_VERSION = "vnext-2026-06"


def fetch_schema(
    backend: BackendClient,
    context: BackendRequestContext,
    *,
    schema_version: str | None = None,
) -> JSONValue:
    params: JSONObject = {}
    if schema_version:
        params["schemaVersion"] = schema_version
    return backend.request(
        "GET",
        context,
        WORKFLOW_SPEC_SCHEMA_PATH,
        params=params or None,
    )


def fetch_spec(
    backend: BackendClient,
    context: BackendRequestContext,
    *,
    app_key: str,
    version_id: str | None = None,
) -> JSONValue:
    params: JSONObject = {"appKey": app_key}
    if version_id:
        params["versionId"] = version_id
    return backend.request("GET", context, WORKFLOW_SPEC_GET_PATH, params=params)


def post_apply(
    backend: BackendClient,
    context: BackendRequestContext,
    *,
    apply_body: JSONObject,
) -> JSONValue:
    return backend.request("POST", context, WORKFLOW_SPEC_APPLY_PATH, json_body=apply_body)


def verify_apply_response(
    *,
    input_spec: JSONObject,
    apply_result: JSONObject,
) -> tuple[JSONObject, list[dict[str, Any]], bool]:
    warnings: list[dict[str, Any]] = []
    applied_spec = apply_result.get("appliedSpec")
    diff_summary = apply_result.get("diffSummary")
    semantic_lint = apply_result.get("semanticLint") if isinstance(apply_result.get("semanticLint"), list) else []

    verification: JSONObject = {
        "applied_spec_available": isinstance(applied_spec, dict),
        "version_snapshot_confirmed": False,
    }

    lint_errors = [
        item
        for item in semantic_lint
        if isinstance(item, dict) and str(item.get("severity") or item.get("level") or "").upper() == "ERROR"
    ]
    if lint_errors:
        verification["semantic_lint_error_count"] = len(lint_errors)
        for item in lint_errors:
            warnings.append(
                {
                    "code": item.get("code") or "SEMANTIC_LINT_ERROR",
                    "message": item.get("message") or "workflow spec semantic lint error",
                    "path": item.get("path"),
                }
            )

    diff_failed = False
    input_ids: set[str] = set()
    if isinstance(input_spec, dict):
        input_ids = {
            str(node.get("id"))
            for node in (input_spec.get("nodes") or [])
            if isinstance(node, dict) and node.get("id") is not None
        }
    if isinstance(diff_summary, dict) and input_ids:
        removed = diff_summary.get("nodes", {}).get("removed") if isinstance(diff_summary.get("nodes"), dict) else None
        if isinstance(removed, list):
            unexpected = [node_id for node_id in removed if str(node_id) in input_ids]
            if unexpected:
                diff_failed = True
                verification["unexpected_removed_node_ids"] = unexpected

    if isinstance(applied_spec, dict) and isinstance(input_spec, dict):
        applied_ids = {
            str(node.get("id"))
            for node in (applied_spec.get("nodes") or [])
            if isinstance(node, dict) and node.get("id") is not None
        }
        missing_input_ids = sorted(node_id for node_id in input_ids if node_id not in applied_ids)
        if missing_input_ids and not diff_failed:
            verification["missing_input_node_ids"] = missing_input_ids

    verified = not lint_errors and not diff_failed and verification.get("applied_spec_available") is True
    return verification, warnings, verified
