"""6G crm.association.create. Contact↔Deal stays provider-specific."""
from __future__ import annotations

from uuid import uuid4

import pytest
from pydantic import ValidationError
from src.domain.codegen.prompt_wrapper import _app_recipes
from src.domain.connectors.catalog import slugs_implementing
from src.domain.connectors.crm_association import (
    ACTION_BY_KIND,
    CONTACT_DEAL_CLASSIFICATION,
    HUBSPOT_DEFAULT_TYPE_ID,
    dispatch_crm_association_plan,
    map_association_input,
    parse_association,
    resolve_crm_association,
    to_public_crm_association_result,
)
from src.domain.connectors.crm_association import (
    CRM_ASSOCIATION_FROZEN_VERBS as RUNTIME_VERBS,
)
from src.domain.connectors.crm_canonical import CRMObjectRef
from src.domain.connectors.crm_resolve import CRMConnectionSnapshot, DispatchAuthorization
from src.domain.connectors.invoke_capabilities import (
    CAPABILITY_CONVERSION_PATTERN,
    CRM_ASSOCIATION_FROZEN_VERBS,
    CRM_ASSOCIATION_OUT_OF_SLICE,
    CRM_ASSOCIATION_PHASE_6G,
    CRM_FAMILY_PLAN,
    CRM_HYPOTHESIZED_VERBS_NOT_FROZEN,
    INVOKE_CAPABILITIES,
    is_implemented,
)
from src.domain.connectors.public_gateway import (
    PHASE_6_STATUS,
    PUBLIC_INVOCABLE_CAPABILITIES,
    strip_capability_response,
)
from src.domain.connectors.router import capability_router
from src.main import app

_CAPS = {"integrations.execute"}
_CONTACT = {"object_type": "contact", "external_id": "c1"}
_COMPANY = {"object_type": "company", "external_id": "co1"}
_DEAL = {"object_type": "deal", "external_id": "d1"}


def _crm(provider: str, **overrides) -> CRMConnectionSnapshot:
    data = dict(
        connection_id=uuid4(),
        connector_key=provider,
        status="active",
        has_secrets=True,
        permission_mode="allow",
        execution_domain="C",
        requires_secrets=True,
        client_side_only=False,
    )
    data.update(overrides)
    return CRMConnectionSnapshot(**data)


def test_6g_freezes_create_for_primary_company_pairs() -> None:
    assert PHASE_6_STATUS == "6p_complete"
    assert CAPABILITY_CONVERSION_PATTERN["next_program_slice"] == (
        "9_app_block_modernization"
    )
    assert CRM_FAMILY_PLAN["association_contact_deal"] == CONTACT_DEAL_CLASSIFICATION
    assert CRM_ASSOCIATION_PHASE_6G["supported_pairs"] == (
        "contact->company",
        "deal->company",
    )
    assert CRM_ASSOCIATION_PHASE_6G["contact_deal"] == "PROVIDER_SPECIFIC_FOR_NOW"
    assert CRM_ASSOCIATION_PHASE_6G["catalog_invocable"] is False
    assert CRM_ASSOCIATION_PHASE_6G["implements_attached"] is False
    assert CRM_ASSOCIATION_FROZEN_VERBS == RUNTIME_VERBS
    spec = INVOKE_CAPABILITIES["crm.association.create"]
    assert spec["status"] == "implemented"
    assert spec["invocable"] is True
    assert spec["method"] == (
        "POST /api/projects/{project_id}/capabilities/crm.association.create"
    )
    assert is_implemented("crm.association.create") is True
    assert "crm.association.create" in PUBLIC_INVOCABLE_CAPABILITIES
    slugs = slugs_implementing("crm.association.create")
    assert "hubspot.associate_objects" in slugs
    assert "salesforce.update_contact" in slugs
    assert "salesforce.update_opportunity" in slugs
    assert "salesforce.associate_objects" not in slugs
    assert "crm.association.create" not in CRM_HYPOTHESIZED_VERBS_NOT_FROZEN
    for verb in CRM_ASSOCIATION_OUT_OF_SLICE:
        assert verb not in INVOKE_CAPABILITIES
    assert "crm.association.create" in _app_recipes()


def test_core_registers_private_association_route() -> None:
    paths = {getattr(route, "path", "") for route in capability_router.routes}
    expected = "/api/projects/{project_id}/capabilities/crm.association.create"
    assert expected in paths
    assert expected in {getattr(route, "path", "") for route in app.routes}
    assert "/api/projects/{project_id}/capabilities/crm.association.delete" not in paths


def test_hubspot_and_salesforce_contact_company_mappings() -> None:
    assoc = parse_association({"from": _CONTACT, "to": _COMPANY})
    assert not isinstance(assoc, str)
    hs = map_association_input("contact_company", "hubspot", assoc)
    assert isinstance(hs, dict)
    assert hs["from_object_type"] == "contacts"
    assert hs["to_object_type"] == "companies"
    assert hs["association_type_id"] == HUBSPOT_DEFAULT_TYPE_ID["contact_company"]
    sf = map_association_input("contact_company", "salesforce", assoc)
    assert sf == {"contact_id": "c1", "fields": {"AccountId": "co1"}}
    assert ACTION_BY_KIND["contact_company"]["salesforce"] == "salesforce.update_contact"


def test_hubspot_and_salesforce_deal_company_mappings() -> None:
    assoc = parse_association({"from": _DEAL, "to": _COMPANY})
    assert not isinstance(assoc, str)
    hs = map_association_input("deal_company", "hubspot", assoc)
    assert isinstance(hs, dict)
    assert hs["from_object_type"] == "deals"
    assert hs["association_type_id"] == HUBSPOT_DEFAULT_TYPE_ID["deal_company"]
    sf = map_association_input("deal_company", "salesforce", assoc)
    assert sf == {"opportunity_id": "d1", "fields": {"AccountId": "co1"}}
    assert ACTION_BY_KIND["deal_company"]["salesforce"] == (
        "salesforce.update_opportunity"
    )


def test_unsupported_pairs_are_rejected() -> None:
    reverse = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _COMPANY, "to": _CONTACT}},
        connections=[_crm("hubspot")],
        enabled_capabilities=_CAPS,
    )
    assert reverse.reason_code == "crm_association_unsupported"
    deal_contact = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _DEAL, "to": _CONTACT}},
        connections=[_crm("salesforce")],
        enabled_capabilities=_CAPS,
    )
    assert deal_contact.reason_code == "crm_association_unsupported"
    assert deal_contact.diagnostics["classification"] == CONTACT_DEAL_CLASSIFICATION
    same = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _COMPANY, "to": _COMPANY}},
        connections=[_crm("hubspot")],
        enabled_capabilities=_CAPS,
    )
    assert same.reason_code == "crm_association_unsupported"


def test_wrong_object_type_and_provider_fields_rejected() -> None:
    with pytest.raises(ValidationError):
        CRMObjectRef(object_type="note", external_id="1")
    invalid = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": "c1", "to": _COMPANY}},
        connections=[_crm("hubspot")],
        enabled_capabilities=_CAPS,
    )
    assert invalid.reason_code == "crm_association_invalid"
    leaked = resolve_crm_association(
        capability="crm.association.create",
        payload={
            "association": {"from": _CONTACT, "to": _COMPANY},
            "association_type_id": 1,
        },
        connections=[_crm("hubspot")],
        enabled_capabilities=_CAPS,
    )
    assert leaked.reason_code == "provider_fields_forbidden"


def test_resolver_reuses_family_rules() -> None:
    unbound = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _CONTACT, "to": _COMPANY}},
        connections=[_crm("hubspot", execution_domain="P")],
        enabled_capabilities=_CAPS,
    )
    assert unbound.reason_code == "crm_unbound"
    two = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _CONTACT, "to": _COMPANY}},
        connections=[_crm("hubspot"), _crm("hubspot")],
        enabled_capabilities=_CAPS,
        provider_hint="hubspot",
    )
    assert two.reason_code == "binding_ambiguous"
    mixed = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _DEAL, "to": _COMPANY}},
        connections=[_crm("hubspot"), _crm("salesforce")],
        enabled_capabilities=_CAPS,
        provider_hint="salesforce",
    )
    assert mixed.ok
    assert mixed.plan is not None
    assert mixed.plan.action == "salesforce.update_opportunity"
    assert mixed.plan.notes["relationship_kind"] == "deal_company"


def test_authorization_is_separate_from_eligibility() -> None:
    resolution = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _CONTACT, "to": _COMPANY}},
        connections=[_crm("hubspot")],
        enabled_capabilities=_CAPS,
    )
    denied = dispatch_crm_association_plan(
        resolution.plan,
        DispatchAuthorization(
            allowed=False,
            reason_code="crm_permission_denied",
            message="execution not authorized",
        ),
        diagnostics=resolution.diagnostics,
    )
    assert denied.ok is False
    assert denied.reason_code == "crm_permission_denied"


def test_queued_and_succeeded_responses() -> None:
    resolution = resolve_crm_association(
        capability="crm.association.create",
        payload={"association": {"from": _CONTACT, "to": _COMPANY}},
        connections=[_crm("hubspot")],
        enabled_capabilities=_CAPS,
    )
    queued = to_public_crm_association_result(
        resolution,
        capability="crm.association.create",
        enqueue={"ok": True, "job_id": "job_1", "status": "queued"},
    ).as_dict()
    assert queued == {"capability": "crm.association.create", "status": "queued"}
    stripped = strip_capability_response(
        {
            **queued,
            "association_type_id": 1,
            "AccountId": "co1",
            "association_id": "fake",
            "job_id": "job_1",
        }
    )
    assert stripped == queued
    succeeded = to_public_crm_association_result(
        resolution,
        capability="crm.association.create",
        enqueue={"ok": True, "result": {"status": "COMPLETE"}},
    )
    assert succeeded.status == "succeeded"
    assert succeeded.association == {"from": _CONTACT, "to": _COMPANY}
    assert "association_type_id" not in succeeded.as_dict()
    public = strip_capability_response(succeeded.as_dict())
    assert "AccountId" not in str(public)
