"""messaging.send is the Codegen default; slack.post_message is an escape hatch."""
from __future__ import annotations

from src.domain.codegen.platform_backend import _compact_connectors
from src.domain.codegen.prompt_wrapper import _app_recipes, _P2HS_CLOUD_CONTRACT, _P2HS_CORE_API_GUIDANCE
from src.domain.connectors.catalog import get_action, slugs_implementing
from src.domain.connectors.invoke_capabilities import (
    CONVERSION_REFERENCE,
    CAPABILITY_CONVERSION_PATTERN,
    MESSAGING_SEND_ERRORS,
    MESSAGING_SEND_FORBIDDEN_METADATA_KEYS,
    MESSAGING_SEND_INPUT_SCHEMA,
    MESSAGING_SEND_INVOKE_METHOD,
    MESSAGING_SEND_RESOLVER_DESIGN,
    SLACK_MESSAGING_SEND_MAPPING,
    is_implemented,
)
from src.domain.manifest.service import _connectors_manifest_envelope


def test_email_remains_the_conversion_reference() -> None:
    assert CONVERSION_REFERENCE["stable_verb"] == "email.send"
    assert CONVERSION_REFERENCE["required_capabilities_change"] is False
    assert CONVERSION_REFERENCE["provider_switch_regenerates_code"] is False
    assert CONVERSION_REFERENCE["generic_codegen_target"] == "capability"
    pattern = CAPABILITY_CONVERSION_PATTERN
    assert pattern["implemented_by"] == "slugs_implementing"
    assert pattern["provider_actions_role"] == "advanced_provider_specific"
    assert pattern["role_default_requires"] == (
        "contract",
        "resolver",
        "invoke_path",
        "provider_implements",
        "codegen_exposure",
    )
    assert pattern["templates"]["email.send"] == "provider_backed_then_codegen_normalize"
    assert pattern["templates"]["messaging.send"] == "contract_then_resolver_invoke_implements_codegen"
    assert pattern["templates"]["messaging.sms.send"] == "provider_backed_then_codegen_normalize"
    assert pattern["opened_families"]["messaging.sms.send"] == "A"
    assert pattern["decision_rule"]["A"].startswith("Live provider/runtime path")
    assert pattern["decision_rule"]["B"].startswith("Stable P2HS verb")
    assert pattern["phase_0_status"] == "complete"
    assert pattern["current_program_phase"] == 8
    assert pattern["opened_families"]["automation.workflow.trigger"] == "A"
    assert pattern["opened_families"]["document.*"] == "A"
    assert pattern["public_api_boundary"] == "dashboard_bff"
    assert pattern["core_is_public_integration_endpoint"] is False
    assert pattern["parked_families"] == {}
    assert pattern["program_order"][0] == "phase_0_public_bff_private_core"
    assert pattern["program_order"][1] == "phase_0a_legacy_capability_alignment"
    assert pattern["program_order"][2] == "automation.workflow.trigger"
    assert pattern["next_candidates"] == ("email.templates",)
    assert pattern["next_candidate_classification"] == {
        "storage.file.*": "split_A_and_B",
        "crm.*": "split_p2hs_records_and_customer_crm",
        "forms.*": "split_p2hs_form_product_and_customer_post_submit",
    }
    assert "spreadsheet.*" not in pattern["closed_families"]
    assert "payments.checkout" not in pattern["deferred"]
    assert pattern["no_capability_state_until_family_opened"] is True
    assert pattern["operating_sequence"][0] == "execution_domain_inventory"
    assert pattern["operating_sequence"][1] == "open_family"
    assert pattern["operating_sequence"][2] == "classify_A_B_or_split"
    assert pattern["operating_sequence"][-1].startswith("move_catalog_invoke_codegen")
    assert pattern["governing_invariant"].startswith("generated app")


def test_slack_post_message_implements_messaging_send() -> None:
    pair = get_action("slack.post_message")
    assert pair is not None
    action = pair[1]
    assert action.implements == ("messaging.send",)
    assert action.required_capabilities == ("integrations.execute",)
    assert slugs_implementing("messaging.send") == ("slack.post_message",)


def test_messaging_send_contract_is_provider_neutral() -> None:
    props = MESSAGING_SEND_INPUT_SCHEMA["properties"]
    assert set(props) == {"destination", "text", "thread_ref", "metadata"}
    assert "blocks" not in props
    assert "channel" not in props
    assert "thread_ts" not in props
    assert "mrkdwn" not in props
    assert MESSAGING_SEND_INPUT_SCHEMA["additionalProperties"] is False
    dest = props["destination"]
    assert dest["x-p2hs"]["not_provider_address"] is True
    assert dest["x-p2hs"]["resolution"] == "runtime_resolver"
    assert "logical" in dest["description"].lower()
    assert "Slack" in dest["description"]
    meta = props["metadata"]
    assert meta["x-p2hs"]["must_not_affect_provider_rendering"] is True
    assert meta["x-p2hs"]["kind"] == "audit_context"
    assert set(MESSAGING_SEND_FORBIDDEN_METADATA_KEYS) >= {"blocks", "attachments", "buttons"}
    assert set(MESSAGING_SEND_ERRORS) >= {
        "destination_invalid",
        "destination_unbound",
        "capability_not_bound",
        "binding_ambiguous",
        "connection_inactive",
        "capability_not_invocable",
        "provider_rejected",
    }


def test_block_kit_stays_on_slack_escape_hatch() -> None:
    mapping = SLACK_MESSAGING_SEND_MAPPING
    assert mapping["status"] == "runtime_adapter"
    assert mapping["provider_action"] == "slack.post_message"
    assert "blocks" in mapping["unmapped_provider_fields"]
    assert "Block Kit" in mapping["escape_hatch"]
    pair = get_action("slack.post_message")
    assert pair is not None
    assert "blocks" in (pair[1].input_schema.get("properties") or {})
    assert "blocks" not in MESSAGING_SEND_INPUT_SCHEMA["properties"]


def test_manifest_exposes_messaging_send_as_codegen_default() -> None:
    envelope = _connectors_manifest_envelope(items={})
    cap = envelope["capabilities"]["messaging.send"]
    assert cap["status"] == "implemented"
    assert cap["role"] == "default"
    assert cap["invocable"] is True
    assert cap["method"] == MESSAGING_SEND_INVOKE_METHOD
    assert cap["request_type"] is None
    assert cap["implemented_by"] == list(slugs_implementing("messaging.send"))
    assert cap["implemented_by"] == ["slack.post_message"]
    assert is_implemented("messaging.send") is True
    slack = envelope["provider_actions"]["slack.post_message"]
    assert slack["role"] == "escape_hatch"
    assert slack["implements"] == ["messaging.send"]
    assert "Block Kit" in slack["when"]
    compact = _compact_connectors(envelope)
    assert compact["capabilities"]["messaging.send"]["role"] == "default"
    assert compact["capabilities"]["messaging.send"]["method"] == MESSAGING_SEND_INVOKE_METHOD
    assert compact["provider_actions"]["slack.post_message"]["role"] == "escape_hatch"


def test_resolver_design_records_codegen_default_and_slack_demotion() -> None:
    design = MESSAGING_SEND_RESOLVER_DESIGN
    assert design["catalog_invocable"] is True
    assert design["implements_attached"] is True
    assert design["codegen_default"] is True
    assert design["codegen_default_unchanged"] is False
    assert design["slack_demoted_to_provider_actions"] is True
    assert design["public_result"] == "capability_shaped"
    assert design["no_platform_default"] is True
    assert design["not_email_first_wins"] is True
    eligibility = design["candidate_eligibility"]
    assert eligibility["connection_status"] == "active"
    assert "slack.post_message" in eligibility["adapter"]
    assert "Twilio" in eligibility["adapter"]
    selection = design["selection"]
    assert selection["zero"] == "capability_not_bound"
    assert "first-wins" in selection["multiple"]
    dest = design["destination_binding"]
    assert dest["provider_shaped_id"].startswith("destination_invalid")
    assert dest["unknown_logical_name"] == "destination_unbound"
    failures = design["failure_semantics"]
    assert set(failures) <= set(MESSAGING_SEND_ERRORS)


def test_codegen_prefers_messaging_send_and_prohibits_generic_slack() -> None:
    recipes = _app_recipes()
    assert "capabilities.messaging.send" in recipes
    assert "Do not emit `POST /api/jobs/messaging.send`" in recipes
    assert "Do not use `slack.post_message` for generic chat" in recipes
    assert "Block Kit" in recipes
    assert "future Teams" in recipes
    assert "POST /api/jobs/messaging.send" in _P2HS_CLOUD_CONTRACT
    assert "Do not use `slack.post_message` for generic chat" in _P2HS_CLOUD_CONTRACT
    assert "Block Kit" in _P2HS_CLOUD_CONTRACT
    assert "Do not use `slack.post_message` for generic chat" in _P2HS_CORE_API_GUIDANCE
    assert "Block Kit" in _P2HS_CORE_API_GUIDANCE


def test_codegen_targets_capability_across_slack_and_teams_bindings() -> None:
    """Same product requirement, two bindings — generated verb stays messaging.send."""
    slack_full = _connectors_manifest_envelope(
        items={"slack": {"actions": [], "provider_actions": ["slack.post_message"]}}
    )
    teams_full = _connectors_manifest_envelope(
        items={"teams": {"actions": ["teams.post_message"], "provider_actions": []}}
    )
    slack_compact = _compact_connectors(slack_full)
    teams_compact = _compact_connectors(teams_full)
    invoke_targets = []
    for surface in (slack_full, teams_full, slack_compact, teams_compact):
        cap = surface["capabilities"]["messaging.send"]
        assert cap["role"] == "default"
        assert cap["invocable"] is True
        assert cap["method"] == MESSAGING_SEND_INVOKE_METHOD
        invoke_targets.append(cap["method"])
    assert len(set(invoke_targets)) == 1
    assert slack_compact["connected"] == ["slack"]
    assert teams_compact["connected"] == ["teams"]
    assert slack_compact["provider_actions"]["slack.post_message"]["role"] == "escape_hatch"
    recipes = _app_recipes()
    assert "capabilities/messaging.send" in recipes
    assert "Do not use `slack.post_message` for generic chat" in recipes
