"""7H Forms post-submit actions: composition after acceptance."""
from __future__ import annotations

from datetime import UTC, datetime
from pathlib import Path

import pytest
from src.domain.connectors.invoke_capabilities import (
    CAPABILITY_CONVERSION_PATTERN,
    FORMS_FAMILY_PLAN,
    INVOKE_CAPABILITIES,
)
from src.domain.forms.actions import (
    CANONICAL_ACTION_TYPES,
    FORMS_POST_SUBMIT_ACTIONS,
    FormCapabilityInvokeResult,
    FormPostSubmitAction,
    FormPostSubmitContext,
    FormPostSubmitPlan,
    RecordingCapabilityInvoker,
    action_idempotency_key,
    contact_compatible_plan,
    execute_post_submit_actions,
    map_action_payload,
    validate_post_submit_plan,
)
from src.domain.forms.anti_abuse import (
    ChallengeVerificationResult,
    FormAntiAbusePolicy,
    InMemoryAttemptLimiter,
    StaticChallengeVerifier,
    process_form_submission_request,
)
from src.domain.forms.canonical import (
    FormDefinition,
    FormField,
    FormFileRef,
    contact_block_compatible_definition,
    quote_request_block_compatible_definition,
)
from src.domain.forms.files import (
    FormFileRecord,
    InMemoryFormFileDirectory,
    accept_form_submission_with_files,
)
from src.domain.forms.persistence import (
    FormPersistenceContext,
    FormPersistenceError,
    InMemoryFormSubmissionStore,
    get_form_submission,
)
from src.domain.forms.runtime import accept_form_submission

_FIXED_AT = datetime(2026, 9, 13, 20, 0, tzinfo=UTC)
_FSB_A = "fsb_" + ("a" * 32)
_FSB_B = "fsb_" + ("b" * 32)
_FILE_1 = "file_" + ("1" * 32)
_CTX = FormPostSubmitContext(project_id="prj_a")


class BoomStore:
    def persist(self, *args, **kwargs):
        raise FormPersistenceError("submission_persistence_failed")

    def get(self, *args, **kwargs):
        return None


def _accept(definition, raw, submission_id=_FSB_A):
    result = accept_form_submission(
        definition,
        raw,
        clock=lambda: _FIXED_AT,
        submission_id_factory=lambda: submission_id,
    )
    assert result.accepted is True
    return result.submission


def _crm_definition() -> FormDefinition:
    return FormDefinition(
        key="lead",
        fields=[
            FormField(key="email", type="email", required=False),
            FormField(key="first_name", type="text", required=False),
            FormField(key="phone", type="phone", required=False),
            FormField(key="favorite_color", type="text", required=False),
            FormField(key="budget", type="number", required=False),
            FormField(key="opt_in", type="checkbox", required=False),
            FormField(key="note", type="textarea", required=False),
        ],
    )


def test_7h_post_submit_frozen() -> None:
    assert FORMS_POST_SUBMIT_ACTIONS["status"] == "frozen"
    assert FORMS_POST_SUBMIT_ACTIONS["not_form_definition"] is True
    assert FORMS_POST_SUBMIT_ACTIONS["acceptance_independent"] is True
    assert FORMS_POST_SUBMIT_ACTIONS["failure_policy"] == "BEST_EFFORT"
    assert FORMS_POST_SUBMIT_ACTIONS["rollback"] is False
    assert FORMS_POST_SUBMIT_ACTIONS["http_forward"] == (
        "internal_cloud_job_not_canonical_form_capability"
    )
    assert FORMS_POST_SUBMIT_ACTIONS["email_templates"] is False
    assert FORMS_POST_SUBMIT_ACTIONS["resolver_in_forms"] is False
    assert "actions" not in FormDefinition.model_fields
    assert "form.submit" not in INVOKE_CAPABILITIES
    assert "form.forward" not in INVOKE_CAPABILITIES
    for capability in CANONICAL_ACTION_TYPES:
        assert capability in INVOKE_CAPABILITIES
    assert CAPABILITY_CONVERSION_PATTERN["phase_7_status"] == "complete"
    assert CAPABILITY_CONVERSION_PATTERN["next_program_slice"] == "9_app_block_modernization"
    assert CAPABILITY_CONVERSION_PATTERN["opened_families"]["forms.*"] == (
        "composition_integration"
    )
    assert FORMS_FAMILY_PLAN["status"] == "complete"
    assert FORMS_FAMILY_PLAN["post_submit_frozen"] is True
    assert FORMS_FAMILY_PLAN["slices"][7] == "7h_post_submit_action_model"
    assert FORMS_FAMILY_PLAN["next_slice"] == "9_app_block_modernization"


def test_contact_plan_is_canonical_and_ordered() -> None:
    plan = contact_compatible_plan()
    assert [action.key for action in plan.actions] == [
        "sync_crm",
        "send_confirmation",
        "notify_automation",
    ]
    assert [action.action_type for action in plan.actions] == [
        "crm.contact.upsert",
        "email.send",
        "automation.workflow.trigger",
    ]
    definition = contact_block_compatible_definition()
    assert validate_post_submit_plan(plan, definition) == ()


def test_duplicate_action_keys_rejected() -> None:
    with pytest.raises(ValueError, match="duplicate_action_key"):
        FormPostSubmitPlan(
            actions=(
                FormPostSubmitAction(key="sync_crm", action_type="crm.contact.upsert"),
                FormPostSubmitAction(key="sync_crm", action_type="email.send"),
            )
        )


def test_crm_upsert_maps_canonical_fields_not_providers() -> None:
    definition = _crm_definition()
    accepted = _accept(
        definition,
        {
            "email": "jane@example.com",
            "first_name": "Jane",
            "favorite_color": "blue",
        },
    )
    plan = FormPostSubmitPlan(
        actions=(
            FormPostSubmitAction(
                key="sync_crm",
                action_type="crm.contact.upsert",
                mapping={"email": "email", "first_name": "first_name"},
            ),
        )
    )
    invoker = RecordingCapabilityInvoker()
    result = execute_post_submit_actions(
        accepted, plan, context=_CTX, invoker=invoker, definition=definition
    )
    assert result.plan_error is None
    assert result.actions[0].status == "succeeded"
    assert invoker.calls[0]["capability"] == "crm.contact.upsert"
    assert invoker.calls[0]["payload"] == {
        "contact": {
            "email": "jane@example.com",
            "first_name": "Jane",
        }
    }
    assert "favorite_color" not in invoker.calls[0]["payload"]["contact"]
    assert "hubspot" not in str(invoker.calls[0]["payload"]).lower()
    assert accepted.submission.values.get("crm_id") is None


def test_no_email_does_not_fabricate_crm_create() -> None:
    definition = _crm_definition()
    accepted = _accept(definition, {"first_name": "Jane"})
    plan = FormPostSubmitPlan(
        actions=(
            FormPostSubmitAction(
                key="sync_crm",
                action_type="crm.contact.upsert",
                mapping={"email": "email", "first_name": "first_name"},
                optional=True,
            ),
        )
    )
    invoker = RecordingCapabilityInvoker()
    result = execute_post_submit_actions(
        accepted, plan, context=_CTX, invoker=invoker, definition=definition
    )
    assert result.actions[0].status == "skipped"
    assert result.actions[0].error_code == "crm_identity_missing"
    assert invoker.calls == []


def test_email_and_automation_map_without_templates_or_webhooks() -> None:
    definition = contact_block_compatible_definition()
    accepted = _accept(
        definition,
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
    )
    invoker = RecordingCapabilityInvoker()
    result = execute_post_submit_actions(
        accepted,
        contact_compatible_plan(),
        context=_CTX,
        invoker=invoker,
        definition=definition,
    )
    by_type = {call["capability"]: call["payload"] for call in invoker.calls}
    assert by_type["email.send"]["to"] == "ada@example.com"
    assert "template_id" not in by_type["email.send"]
    assert by_type["automation.workflow.trigger"]["workflow"] == "lead-intake"
    assert by_type["automation.workflow.trigger"]["input"]["email"] == "ada@example.com"
    assert "webhook_url" not in str(by_type["automation.workflow.trigger"])
    assert result.submission_id == _FSB_A


def test_provider_specific_escape_hatch_is_explicit() -> None:
    definition = _crm_definition()
    accepted = _accept(definition, {"email": "jane@example.com"})
    plan = FormPostSubmitPlan(
        actions=(
            FormPostSubmitAction(
                key="legacy_hs",
                action_type="hubspot.upsert_contact",
                mapping={"email": "email"},
                provider_specific=True,
            ),
        )
    )
    assert validate_post_submit_plan(plan, definition) == ()
    invoker = RecordingCapabilityInvoker()
    execute_post_submit_actions(
        accepted, plan, context=_CTX, invoker=invoker, definition=definition
    )
    assert invoker.calls[0]["capability"] == "hubspot.upsert_contact"


def test_unknown_action_type_executes_nothing() -> None:
    definition = _crm_definition()
    accepted = _accept(definition, {"email": "jane@example.com"})
    plan = FormPostSubmitPlan(
        actions=(
            FormPostSubmitAction(key="weird", action_type="not.a.capability"),
        )
    )
    invoker = RecordingCapabilityInvoker()
    result = execute_post_submit_actions(
        accepted, plan, context=_CTX, invoker=invoker, definition=definition
    )
    assert result.plan_error == "unknown_action_type"
    assert result.actions == ()
    assert invoker.calls == []


def test_partial_failure_does_not_rollback_acceptance_or_persist() -> None:
    definition = contact_block_compatible_definition()
    accepted = _accept(
        definition,
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
    )
    store = InMemoryFormSubmissionStore()
    invoker = RecordingCapabilityInvoker(
        {
            "email.send": FormCapabilityInvokeResult(
                status="failed",
                error_code="email_delivery_failed",
            ),
            "automation.workflow.trigger": FormCapabilityInvokeResult(
                status="succeeded",
                execution_ref="wf_1",
            ),
            "crm.contact.upsert": FormCapabilityInvokeResult(
                status="succeeded",
                execution_ref="crm_1",
            ),
        }
    )
    result = execute_post_submit_actions(
        accepted,
        contact_compatible_plan(),
        context=_CTX,
        invoker=invoker,
        definition=definition,
        store=store,
        persist_first=True,
    )
    statuses = {item.action_key: item.status for item in result.actions}
    assert statuses == {
        "sync_crm": "succeeded",
        "send_confirmation": "failed",
        "notify_automation": "succeeded",
    }
    assert result.actions[1].error_code == "email_delivery_failed"
    row = get_form_submission(
        store, FormPersistenceContext(project_id="prj_a"), _FSB_A
    )
    assert row is not None
    assert row.accepted.submission_id == _FSB_A
    assert "crm_id" not in row.accepted.submission.values


def test_idempotency_is_submission_plus_action_key() -> None:
    assert action_idempotency_key(_FSB_A, "send_confirmation") == (
        f"{_FSB_A}:send_confirmation"
    )
    definition = contact_block_compatible_definition()
    raw = {"name": "Ada", "email": "ada@example.com", "message": "Hello"}
    a = _accept(definition, raw, _FSB_A)
    b = _accept(definition, raw, _FSB_B)
    plan = contact_compatible_plan()
    invoker = RecordingCapabilityInvoker()
    execute_post_submit_actions(
        a, plan, context=_CTX, invoker=invoker, definition=definition
    )
    execute_post_submit_actions(
        b, plan, context=_CTX, invoker=invoker, definition=definition
    )
    keys = [call["idempotency_key"] for call in invoker.calls]
    assert f"{_FSB_A}:send_confirmation" in keys
    assert f"{_FSB_B}:send_confirmation" in keys
    assert keys.count(f"{_FSB_A}:send_confirmation") == 1


def test_missing_null_false_and_zero_mapping() -> None:
    action = FormPostSubmitAction(
        key="sync_crm",
        action_type="crm.contact.upsert",
        mapping={
            "email": "email",
            "first_name": "first_name",
            "opt_in": "opt_in",
            "budget": "budget",
            "note": "note",
        },
    )
    payload = map_action_payload(
        action,
        {
            "first_name": "Jane",
            "opt_in": False,
            "budget": 0,
            "note": None,
        },
    )
    assert "email" not in payload
    assert payload["first_name"] == "Jane"
    assert payload["opt_in"] is False
    assert payload["budget"] == 0
    assert payload["note"] is None
    accepted = _accept(_crm_definition(), {"opt_in": False, "budget": 0})
    assert accepted.submission.values["opt_in"] is False
    assert accepted.submission.values["budget"] == 0


def test_file_ref_mapping_stays_opaque() -> None:
    definition = quote_request_block_compatible_definition()
    files = InMemoryFormFileDirectory()
    files.put(FormFileRecord(project_id="prj_a", file_id=_FILE_1))
    runtime = accept_form_submission_with_files(
        definition,
        {
            "name": "Ada",
            "email": "ada@example.com",
            "message": "Quote",
            "attachment": {"file_id": _FILE_1},
        },
        project_id="prj_a",
        files=files,
        clock=lambda: _FIXED_AT,
        submission_id_factory=lambda: _FSB_A,
    )
    action = FormPostSubmitAction(
        key="forward",
        action_type="internal.http_forward",
        mapping={"attachment": "attachment", "email": "email"},
    )
    payload = map_action_payload(action, runtime.submission.submission.values)
    assert payload["attachment"] == {"file_id": _FILE_1}
    assert "bucket" not in payload
    assert isinstance(runtime.submission.submission.values["attachment"], FormFileRef)


def test_no_actions_on_anti_abuse_or_validation_failure() -> None:
    invoker = RecordingCapabilityInvoker()
    rejected = process_form_submission_request(
        contact_block_compatible_definition(),
        {
            "name": "Ada",
            "email": "ada@example.com",
            "message": "Hi",
            "website": "http://bot",
        },
        policy=FormAntiAbusePolicy(),
        project_id="prj_a",
        client_identity="net_a",
        verifier=StaticChallengeVerifier(ChallengeVerificationResult(passed=True)),
        limiter=InMemoryAttemptLimiter(),
        clock=lambda: _FIXED_AT,
        submission_id_factory=lambda: _FSB_A,
    )
    assert rejected.accepted is False
    assert invoker.calls == []
    invalid = accept_form_submission(
        contact_block_compatible_definition(),
        {"email": "nope"},
        clock=lambda: _FIXED_AT,
        submission_id_factory=lambda: _FSB_A,
    )
    assert invalid.accepted is False
    assert invalid.submission is None


def test_persist_failure_skips_external_actions() -> None:
    definition = contact_block_compatible_definition()
    accepted = _accept(
        definition,
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
    )
    invoker = RecordingCapabilityInvoker()
    result = execute_post_submit_actions(
        accepted,
        contact_compatible_plan(),
        context=_CTX,
        invoker=invoker,
        definition=definition,
        store=BoomStore(),
        persist_first=True,
    )
    assert result.plan_error == "persistence_failed"
    assert result.actions == ()
    assert invoker.calls == []


def test_http_forward_is_internal_not_capability_family() -> None:
    definition = contact_block_compatible_definition()
    accepted = _accept(
        definition,
        {"name": "Ada", "email": "ada@example.com", "message": "Hello"},
    )
    plan = FormPostSubmitPlan(
        actions=(
            FormPostSubmitAction(
                key="forward",
                action_type="internal.http_forward",
                mapping={"message": "message"},
                constants={"target": "https://example.com/webhook"},
            ),
        )
    )
    invoker = RecordingCapabilityInvoker(
        {
            "internal.http_forward": FormCapabilityInvokeResult(
                status="queued",
                execution_ref="job_1",
            )
        }
    )
    result = execute_post_submit_actions(
        accepted, plan, context=_CTX, invoker=invoker, definition=definition
    )
    assert result.actions[0].status == "queued"
    assert result.actions[0].execution_ref == "job_1"
    assert "form.forward" not in INVOKE_CAPABILITIES


def test_actions_module_stays_provider_neutral() -> None:
    source = Path("src/domain/forms/actions.py").read_text(encoding="utf-8")
    lowered = source.lower()
    for token in (
        "resend",
        "mailgun",
        "salesforce",
        "make_com",
        "n8n",
        "sqlalchemy",
        "src.domain.connectors",
        "src.integrations",
        "httpx",
    ):
        assert token not in source and token not in lowered
    assert "hubspot" not in lowered
    dumped = FormPostSubmitAction(
        key="sync_crm",
        action_type="crm.contact.upsert",
    ).model_dump()
    assert "connection_id" not in dumped
    assert "portal_id" not in dumped
