"""Shared [C] CRM connection selection. Object mapping stays in contact/company modules.

Eligibility is not execution authorization.
"""
from __future__ import annotations

import base64
from dataclasses import dataclass, field
from typing import Any
from uuid import UUID

CRM_PROVIDERS = frozenset({"hubspot", "salesforce"})
HUBSPOT_NULL_CLEAR = "empty_string_crm_v3"


@dataclass(frozen=True)
class CRMConnectionSnapshot:
    connection_id: UUID
    connector_key: str
    status: str
    has_secrets: bool
    permission_mode: str
    execution_domain: str = "C"
    requires_secrets: bool = True
    client_side_only: bool = False
    config: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class CRMPlan:
    capability: str
    connection_id: UUID
    provider: str
    action: str
    resolved_input: dict[str, Any]
    notes: dict[str, Any] = field(default_factory=dict)

    def integration_execute_payload(self) -> dict[str, Any]:
        return {
            "request_type": "integration.execute",
            "connection_id": str(self.connection_id),
            "action": self.action,
            "input": dict(self.resolved_input),
        }


@dataclass(frozen=True)
class CRMResolution:
    ok: bool
    reason_code: str | None = None
    message: str | None = None
    plan: CRMPlan | None = None
    diagnostics: dict[str, Any] = field(default_factory=dict)


@dataclass(frozen=True)
class DispatchAuthorization:
    allowed: bool
    reason_code: str | None = None
    message: str | None = None


def fail_crm(
    code: str, message: str, diagnostics: dict[str, Any] | None = None
) -> CRMResolution:
    return CRMResolution(
        ok=False, reason_code=code, message=message, diagnostics=diagnostics or {}
    )


def encode_crm_cursor(provider: str, token: str) -> str:
    packed = f"{provider}|{token}".encode()
    return base64.urlsafe_b64encode(packed).decode().rstrip("=")


def decode_crm_cursor(provider: str, cursor: str) -> str | None:
    pad = "=" * (-len(cursor) % 4)
    try:
        decoded = base64.urlsafe_b64decode(cursor + pad).decode()
    except (ValueError, UnicodeDecodeError):
        return None
    prefix = f"{provider}|"
    if not decoded.startswith(prefix):
        return None
    token = decoded[len(prefix) :]
    return token or None


def _ineligibility_reason(
    row: CRMConnectionSnapshot,
    capability: str,
    action_by_capability: dict[str, dict[str, str]],
    enabled_capabilities: set[str] | None,
    approval_present: bool,
) -> str | None:
    if row.execution_domain != "C":
        return "execution_domain_not_customer"
    if row.connector_key not in CRM_PROVIDERS:
        return "adapter_not_planned"
    if row.client_side_only:
        return "client_side_only"
    if row.status != "active":
        return "connection_inactive"
    if row.requires_secrets and not row.has_secrets:
        return "credentials_missing"
    if {"integrations.execute"} - (enabled_capabilities or set()):
        return "entitlement_missing"
    actions = action_by_capability.get(capability) or {}
    if row.connector_key not in actions:
        return "adapter_not_planned"
    mode = (row.permission_mode or "deny").lower()
    if mode == "deny":
        return "permission_denied"
    if mode == "ask" and not approval_present:
        return "approval_required"
    return None


def select_crm_connection(
    *,
    capability: str,
    connections: list[CRMConnectionSnapshot],
    action_by_capability: dict[str, dict[str, str]],
    enabled_capabilities: set[str] | None = None,
    provider_hint: str | None = None,
    binding_connection_id: UUID | None = None,
    approval_present: bool = False,
) -> CRMResolution:
    """Pick the unique eligible customer HubSpot or Salesforce connection."""
    diagnostics: dict[str, Any] = {"rejected": []}
    candidates = [
        row
        for row in connections
        if row.connector_key in CRM_PROVIDERS and row.execution_domain == "C"
    ]
    hint = (provider_hint or "").strip().lower()
    if hint:
        if hint not in CRM_PROVIDERS:
            return fail_crm("crm_unbound", "provider_hint must be hubspot or salesforce")
        candidates = [row for row in candidates if row.connector_key == hint]

    eligible: list[CRMConnectionSnapshot] = []
    for row in candidates:
        reason = _ineligibility_reason(
            row,
            capability,
            action_by_capability,
            enabled_capabilities,
            approval_present,
        )
        if reason:
            diagnostics["rejected"].append(
                {
                    "connection_id": str(row.connection_id),
                    "connector_key": row.connector_key,
                    "reason": reason,
                }
            )
            continue
        eligible.append(row)

    if binding_connection_id is not None:
        intended = next(
            (row for row in connections if row.connection_id == binding_connection_id),
            None,
        )
        if intended is None or intended.connector_key not in CRM_PROVIDERS:
            return fail_crm(
                "crm_unbound",
                "Explicit CRM binding is not a customer HubSpot or Salesforce connection",
                diagnostics,
            )
        if intended.execution_domain != "C":
            return fail_crm(
                "crm_unbound",
                "CRM objects do not resolve P2HS records",
                diagnostics,
            )
        reason = _ineligibility_reason(
            intended,
            capability,
            action_by_capability,
            enabled_capabilities,
            approval_present,
        )
        if reason:
            return fail_crm(
                "crm_unbound",
                "Bound CRM connection is not eligible",
                {**diagnostics, "reason": reason},
            )
        eligible = [intended]

    if not eligible:
        return fail_crm(
            "crm_unbound",
            "No eligible customer HubSpot or Salesforce connection",
            diagnostics,
        )
    if len(eligible) > 1:
        return fail_crm(
            "binding_ambiguous",
            "Multiple eligible CRM connections; provider_hint filters family only",
            {
                **diagnostics,
                "eligible": [
                    {
                        "connection_id": str(row.connection_id),
                        "connector_key": row.connector_key,
                    }
                    for row in eligible
                ],
            },
        )

    row = eligible[0]
    return CRMResolution(
        ok=True,
        plan=CRMPlan(
            capability=capability,
            connection_id=row.connection_id,
            provider=row.connector_key,
            action=action_by_capability[capability][row.connector_key],
            resolved_input={},
            notes={"execution_domain": "C", "salesforce_lead_excluded": True},
        ),
        diagnostics=diagnostics,
    )


def dispatch_crm_plan(
    plan: CRMPlan,
    authorization: DispatchAuthorization,
    *,
    diagnostics: dict[str, Any] | None = None,
) -> CRMResolution:
    notes = dict(diagnostics or {})
    notes["payload"] = dict(plan.resolved_input)
    if not authorization.allowed:
        notes["authorization"] = "rejected"
        code = authorization.reason_code or "crm_permission_denied"
        return fail_crm(
            map_enqueue_error(code, capability=plan.capability),
            authorization.message or "integration.execute rejected the CRM plan",
            notes,
        )
    notes["authorization"] = "allowed"
    return CRMResolution(ok=True, plan=plan, diagnostics=notes)


def map_enqueue_error(code: str, *, capability: str = "") -> str:
    mapped = {
        "permission_denied": "crm_permission_denied",
        "connector_action_denied": "crm_permission_denied",
        "scope_missing": "reauthorization_required",
        "credentials_missing": "reauthorization_required",
        "rate_limited": "crm_rate_limited",
        "crm_rate_limited": "crm_rate_limited",
        "provider_unavailable": "crm_provider_unavailable",
        "crm_provider_unavailable": "crm_provider_unavailable",
        "provider_rejected": "crm_validation_failed",
        "reauthorization_required": "reauthorization_required",
        "crm_permission_denied": "crm_permission_denied",
        "crm_validation_failed": "crm_validation_failed",
        "crm_property_invalid": "crm_property_invalid",
        "crm_duplicate_match": "crm_duplicate_match",
        "crm_not_found": "crm_not_found",
        "connection_inactive": "reauthorization_required",
    }.get(code, code)
    if mapped in {"crm_not_found", "not_found"}:
        if capability.startswith("crm.contact"):
            return "crm_contact_not_found"
        if capability.startswith("crm.company"):
            return "crm_company_not_found"
        if capability.startswith("crm.deal"):
            return "crm_deal_not_found"
    return mapped


def enqueue_output(enqueue: dict[str, Any]) -> dict[str, Any] | None:
    output = None
    for key in ("deal", "company", "contact", "result", "output"):
        value = enqueue.get(key)
        if isinstance(value, dict) and value:
            output = value
            break
    if output is not None and set(output) <= {"job_id", "status", "ok", "queued"}:
        return None
    return output
