from __future__ import annotations

import copy
import hashlib
import json
from typing import Any

from sqlalchemy import text
from sqlalchemy.exc import IntegrityError

from app.core.common.identifiers import ensure_governance_uid, new_governance_uid


ENVIRONMENTS = {"development", "test", "production"}
VOLATILE_KEYS = {"active", "createdAt", "updatedAt", "versionId"}
SECRET_KEYS = {"credentials", "credential", "apiKey", "password", "token", "authorization"}


def validate_dataflow_uid(value: str) -> str:
    properties = {"uid": str(value)}
    return ensure_governance_uid(properties)


def _sanitize(value: Any, key: str | None = None) -> Any:
    if key in VOLATILE_KEYS:
        return None
    if key in SECRET_KEYS:
        return "[redacted]"
    if isinstance(value, dict):
        return {
            item_key: sanitized
            for item_key, item_value in sorted(value.items())
            if item_key not in VOLATILE_KEYS
            for sanitized in [_sanitize(item_value, item_key)]
        }
    if isinstance(value, list):
        return [_sanitize(item) for item in value]
    return copy.deepcopy(value)


def safe_workflow_snapshot(workflow: dict[str, Any]) -> dict[str, Any]:
    return _sanitize(workflow)


def workflow_hash(workflow: dict[str, Any]) -> str:
    canonical = json.dumps(
        safe_workflow_snapshot(workflow), sort_keys=True, separators=(",", ":"), ensure_ascii=False
    )
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


def create_version(
    session,
    *,
    dataflow_uid: str,
    environment: str,
    workflow: dict[str, Any],
    created_by: str,
) -> str:
    dataflow_uid = validate_dataflow_uid(dataflow_uid)
    if environment not in ENVIRONMENTS:
        raise ValueError("unsupported environment")
    n8n_id = str(workflow.get("id") or "").strip()
    if not n8n_id:
        raise ValueError("n8n workflow id is required")
    snapshot = safe_workflow_snapshot(workflow)
    digest = workflow_hash(workflow)
    session.execute(
        text("SELECT pg_advisory_xact_lock(hashtext(:key))"),
        {"key": f"{dataflow_uid}:{environment}"},
    )
    version_no = session.execute(
        text(
            "SELECT COALESCE(MAX(version_no), 0) + 1 FROM public.dataflow_workflow_versions "
            "WHERE dataflow_uid = CAST(:uid AS uuid) AND environment = :environment"
        ),
        {"uid": dataflow_uid, "environment": environment},
    ).scalar_one()
    version_id = new_governance_uid()
    try:
        session.execute(
            text(
                "INSERT INTO public.dataflow_workflow_versions "
                "(id, dataflow_uid, environment, version_no, n8n_workflow_id, "
                "n8n_workflow_name, definition_hash, definition_snapshot, created_by) "
                "VALUES (CAST(:id AS uuid), CAST(:uid AS uuid), :environment, :version_no, "
                ":n8n_id, :name, :digest, CAST(:snapshot AS jsonb), CAST(:created_by AS uuid))"
            ),
            {
                "id": version_id, "uid": dataflow_uid, "environment": environment,
                "version_no": version_no, "n8n_id": n8n_id, "name": workflow.get("name"),
                "digest": digest, "snapshot": json.dumps(snapshot, ensure_ascii=False),
                "created_by": created_by,
            },
        )
    except IntegrityError as exc:
        raise ValueError("this workflow definition already has a version") from exc
    return version_id


def list_versions(session, dataflow_uid: str) -> list[dict[str, Any]]:
    uid = validate_dataflow_uid(dataflow_uid)
    rows = session.execute(
        text(
            "SELECT id::text, dataflow_uid::text, environment, version_no, n8n_workflow_id, "
            "n8n_workflow_name, definition_hash, status, activation_error, created_at, activated_at "
            "FROM public.dataflow_workflow_versions WHERE dataflow_uid = CAST(:uid AS uuid) "
            "ORDER BY environment, version_no DESC"
        ),
        {"uid": uid},
    )
    keys = ("id", "dataflow_uid", "environment", "version_no", "n8n_workflow_id", "n8n_workflow_name", "definition_hash", "status", "activation_error", "created_at", "activated_at")
    return [{key: (value.isoformat() if hasattr(value, "isoformat") else value) for key, value in zip(keys, row)} for row in rows]
