"""Forms → canonical automation.workflow.trigger. Not a workflow engine.

Maps accepted form values onto the frozen automation capability.
Make/n8n selection stays in the existing resolver.
"""
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from pydantic import BaseModel, ConfigDict, StrictBool, StrictStr

from src.domain.connectors.automation_workflow import normalize_logical_workflow
from src.domain.forms.actions import FormPostSubmitAction
from src.domain.forms.canonical import (
    STRING_FIELD_TYPES,
    FormDefinition,
    FormFileRef,
    parse_form_file_ref,
)

GENERIC_AUTOMATION_ACTION = "automation.workflow.trigger"
ALLOWED_PROVIDER_HINTS: frozenset[str] = frozenset({"make_com", "n8n"})
AUTOMATION_VALUE_FIELD_TYPES: frozenset[str] = STRING_FIELD_TYPES | {"number", "checkbox"}
FILE_POLICY = "disallow"

CONTROL_TARGETS: frozenset[str] = frozenset(
    {
        "workflow",
        "provider_hint",
        "binding",
        "binding_connection_id",
        "connection_id",
        "metadata",
        "webhook_url",
        "hook_url",
        "scenario_id",
        "workflow_id",
        "zap_id",
        "payload",
        "idempotency_key",
        "make_payload",
        "n8n_body",
        "target",
        "target_url",
        "url",
    }
)
ALLOWED_AUTOMATION_CONSTANTS: frozenset[str] = frozenset({"workflow", "provider_hint"})

CONTACT_COMPATIBLE_AUTOMATION_MAPPING: dict[str, str] = {
    "email": "email",
    "phone": "phone",
    "message": "message",
}
CONTACT_COMPATIBLE_WORKFLOW = "lead-intake"

FORMS_AUTOMATION_COMPOSITION: dict[str, Any] = {
    "slice_id": "7k_automation_webhook_composition",
    "status": "frozen",
    "generic_action": GENERIC_AUTOMATION_ACTION,
    "execution_domain": "C",
    "workflow_identity": "logical_alias",
    "resolver_in_forms": False,
    "file_policy": FILE_POLICY,
    "nested_mapping": False,
    "routing_from_form_values": False,
    "public_bff": False,
    "form_submit_capability": False,
    "codegen": False,
    "app_block_migrated": False,
    "docs": "docs/audits/FORMS_AUTOMATION_WEBHOOK_COMPOSITION_7K.md",
}


class AutomationTriggerBuild(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True)
    skipped: StrictBool
    payload: dict[str, Any] | None = None
    error_code: StrictStr | None = None


def automation_workflow_action(
    *,
    key: str = "notify_automation",
    mapping: Mapping[str, str] | None = None,
    workflow: str = CONTACT_COMPATIBLE_WORKFLOW,
    provider_hint: str | None = None,
    optional: bool = True,
) -> FormPostSubmitAction:
    constants: dict[str, Any] = {"workflow": workflow}
    if provider_hint is not None:
        constants["provider_hint"] = provider_hint
    return FormPostSubmitAction(
        key=key,
        action_type=GENERIC_AUTOMATION_ACTION,
        mapping=dict(mapping or CONTACT_COMPATIBLE_AUTOMATION_MAPPING),
        constants=constants,
        optional=optional,
    )


def _source_field(definition: FormDefinition, key: str):
    for field in definition.fields:
        if field.key == key:
            return field
    return None


def validate_automation_mapping(
    action: FormPostSubmitAction,
    definition: FormDefinition,
) -> tuple[str, ...]:
    if action.action_type != GENERIC_AUTOMATION_ACTION or action.provider_specific:
        return ()
    errors: list[str] = []
    seen: set[str] = set()
    for target, source in action.mapping.items():
        if "." in target or "[" in target:
            errors.append("nested_automation_target")
            continue
        if target in CONTROL_TARGETS:
            errors.append("routing_from_form_values")
            continue
        if target in seen:
            errors.append("duplicate_automation_target")
            continue
        seen.add(target)
        field = _source_field(definition, source)
        if field is None:
            errors.append("unknown_source_field")
            continue
        if field.type == "file":
            errors.append("unsupported_automation_file_mapping")
            continue
        if field.type not in AUTOMATION_VALUE_FIELD_TYPES:
            errors.append("unsupported_automation_source_type")
    for key, value in action.constants.items():
        if key not in ALLOWED_AUTOMATION_CONSTANTS:
            errors.append("unexpected_automation_constant")
            continue
        if key == "workflow":
            if not isinstance(value, str) or normalize_logical_workflow(value) is None:
                errors.append("workflow_invalid")
        if key == "provider_hint":
            if not isinstance(value, str) or value not in ALLOWED_PROVIDER_HINTS:
                errors.append("unexpected_automation_constant")
    workflow = action.constants.get("workflow")
    if not isinstance(workflow, str) or not workflow.strip():
        errors.append("missing_workflow_constant")
    return tuple(dict.fromkeys(errors))


def _is_file_value(value: Any) -> bool:
    if isinstance(value, FormFileRef):
        return True
    return parse_form_file_ref(value) is not None


def build_automation_trigger_payload(
    action: FormPostSubmitAction,
    values: Mapping[str, Any],
) -> AutomationTriggerBuild:
    """Canonical {workflow, input} plus optional trusted provider_hint."""
    mapped: dict[str, Any] = {}
    for target, source in action.mapping.items():
        if target in CONTROL_TARGETS or "." in target or "[" in target:
            return AutomationTriggerBuild(
                skipped=False,
                error_code="routing_from_form_values",
            )
        if source not in values:
            continue
        value = values[source]
        if _is_file_value(value):
            return AutomationTriggerBuild(
                skipped=False,
                error_code="unsupported_automation_file_mapping",
            )
        mapped[target] = value
    workflow_raw = action.constants.get("workflow")
    if not isinstance(workflow_raw, str):
        return AutomationTriggerBuild(skipped=False, error_code="missing_workflow_constant")
    workflow = normalize_logical_workflow(workflow_raw)
    if workflow is None:
        return AutomationTriggerBuild(skipped=False, error_code="workflow_invalid")
    payload: dict[str, Any] = {"workflow": workflow, "input": mapped}
    hint = action.constants.get("provider_hint")
    if hint is not None:
        if hint not in ALLOWED_PROVIDER_HINTS:
            return AutomationTriggerBuild(
                skipped=False,
                error_code="unexpected_automation_constant",
            )
        payload["provider_hint"] = hint
    return AutomationTriggerBuild(skipped=False, payload=payload)
