from __future__ import annotations

import hashlib
import json
from typing import Any, Optional

from sqlalchemy import text
from sqlalchemy.exc import IntegrityError

from app.core.common.identifiers import ensure_governance_uid, new_governance_uid
from app.core.orchestration.models import EngineType
from app.core.orchestration.spec import (
    validate_schedule_plan,
    validate_workflow_spec,
    workflow_spec_hash,
)


ENVIRONMENTS = {"development", "test", "production"}


def _validate_uid(value: str, label: str) -> str:
    try:
        return ensure_governance_uid({"uid": str(value)})
    except ValueError as exc:
        raise ValueError(f"{label} must be a valid UUIDv7") from exc


def _required_string(value: Any, label: str, maximum: int = 255) -> str:
    if not isinstance(value, str) or not value.strip():
        raise ValueError(f"{label} is required")
    normalized = value.strip()
    if len(normalized) > maximum:
        raise ValueError(f"{label} exceeds {maximum} characters")
    return normalized


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


def create_workflow_version(
    session,
    *,
    dataflow_uid: str,
    environment: str,
    workflow_spec: dict[str, Any],
    schedule_plan: dict[str, Any],
    engine_type: str,
    engine_definition_id: str,
    created_by: str,
    engine_revision: Optional[str] = None,
    deployment_metadata: Optional[dict[str, Any]] = None,
) -> str:
    uid = _validate_uid(dataflow_uid, "dataflow_uid")
    actor_uid = _validate_uid(created_by, "created_by")
    if environment not in ENVIRONMENTS:
        raise ValueError("unsupported environment")
    try:
        normalized_engine = EngineType(engine_type)
    except ValueError as exc:
        raise ValueError("unsupported engine_type") from exc
    definition_id = _required_string(
        engine_definition_id, "engine_definition_id"
    )
    revision = (
        _required_string(engine_revision, "engine_revision")
        if engine_revision is not None
        else None
    )
    workflow = validate_workflow_spec(workflow_spec)
    if workflow["dataflow_uid"] != uid:
        raise ValueError("workflow spec dataflow_uid does not match repository dataflow_uid")
    schedule = validate_schedule_plan(schedule_plan)
    metadata = deployment_metadata or {}
    if not isinstance(metadata, dict):
        raise ValueError("deployment_metadata must be an object")

    session.execute(
        text("SELECT pg_advisory_xact_lock(hashtext(:key))"),
        {"key": f"{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": uid, "environment": environment},
    ).scalar_one()

    version_id = new_governance_uid()
    binding_id = new_governance_uid()
    schedule_id = new_governance_uid()
    workflow_json = json.dumps(workflow, ensure_ascii=False)
    schedule_json = json.dumps(schedule, ensure_ascii=False)
    n8n_id = definition_id if normalized_engine is EngineType.N8N else None
    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, status, "
                "created_by, engine_type, engine_definition_id, engine_revision, "
                "deployment_metadata, workflow_spec, schedule_plan) "
                "VALUES (CAST(:id AS uuid), CAST(:uid AS uuid), :environment, "
                ":version_no, :n8n_id, :name, :definition_hash, "
                "CAST(:definition_snapshot AS jsonb), 'draft', "
                "CAST(:created_by AS uuid), :engine_type, :engine_definition_id, "
                ":engine_revision, CAST(:deployment_metadata AS jsonb), "
                "CAST(:workflow_spec AS jsonb), CAST(:schedule_plan AS jsonb))"
            ),
            {
                "id": version_id,
                "uid": uid,
                "environment": environment,
                "version_no": version_no,
                "n8n_id": n8n_id,
                "name": workflow["name"] if n8n_id else None,
                "definition_hash": workflow_spec_hash(workflow),
                "definition_snapshot": workflow_json,
                "created_by": actor_uid,
                "engine_type": normalized_engine.value,
                "engine_definition_id": definition_id,
                "engine_revision": revision,
                "deployment_metadata": json.dumps(metadata, ensure_ascii=False),
                "workflow_spec": workflow_json,
                "schedule_plan": schedule_json,
            },
        )
        session.execute(
            text(
                "INSERT INTO public.workflow_engine_bindings "
                "(id, workflow_version_id, dataflow_uid, environment, engine_type, "
                "engine_definition_id, engine_revision, role, status, metadata) "
                "VALUES (CAST(:id AS uuid), CAST(:version_id AS uuid), "
                "CAST(:uid AS uuid), :environment, :engine_type, "
                ":engine_definition_id, :engine_revision, 'standby', 'disabled', "
                "CAST(:metadata AS jsonb))"
            ),
            {
                "id": binding_id,
                "version_id": version_id,
                "uid": uid,
                "environment": environment,
                "engine_type": normalized_engine.value,
                "engine_definition_id": definition_id,
                "engine_revision": revision,
                "metadata": json.dumps(metadata, ensure_ascii=False),
            },
        )
        session.execute(
            text(
                "INSERT INTO public.workflow_schedules "
                "(id, workflow_version_id, schedule_plan, schedule_hash, timezone, "
                "status, created_by) "
                "VALUES (CAST(:id AS uuid), CAST(:version_id AS uuid), "
                "CAST(:schedule_plan AS jsonb), :schedule_hash, :timezone, 'draft', "
                "CAST(:created_by AS uuid))"
            ),
            {
                "id": schedule_id,
                "version_id": version_id,
                "schedule_plan": schedule_json,
                "schedule_hash": _schedule_hash(schedule),
                "timezone": schedule["timezone"],
                "created_by": actor_uid,
            },
        )
    except IntegrityError as exc:
        raise ValueError("workflow version conflicts with an existing version") from exc
    return version_id
