"""crm.company.create / get / update / search. Public catalog truth stays 6M.

CRMCompany → HubSpot Company / Salesforce Account.
Salesforce Website is not canonical domain; domain on Salesforce is rejected.
Search is exact Name only. Upsert stays provider-specific.
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Any
from uuid import UUID

from pydantic import ValidationError

from src.domain.connectors.crm_canonical import (
    CRMCanonicalError,
    CRMCompanyInput,
    CRMObjectRef,
    dump_crm_input,
)
from src.domain.connectors.crm_resolve import (
    HUBSPOT_NULL_CLEAR,
    CRMConnectionSnapshot,
    CRMPlan,
    CRMResolution,
    DispatchAuthorization,
    decode_crm_cursor,
    dispatch_crm_plan,
    encode_crm_cursor,
    enqueue_output,
    fail_crm,
    map_enqueue_error,
    select_crm_connection,
)

CRM_COMPANY_FROZEN_VERBS = (
    "crm.company.create",
    "crm.company.get",
    "crm.company.update",
    "crm.company.search",
)
CRM_COMPANY_UPSERT_CLASSIFICATION = "PROVIDER_SPECIFIC_FOR_NOW"
HUBSPOT_COMPANY_FIELDS = {
    "name": "name",
    "domain": "domain",
    "phone": "phone",
}
SALESFORCE_COMPANY_FIELDS = {
    "name": "Name",
    "phone": "Phone",
}
ACTION_BY_CAPABILITY: dict[str, dict[str, str]] = {
    "crm.company.create": {
        "hubspot": "hubspot.create_company",
        "salesforce": "salesforce.create_account",
    },
    "crm.company.get": {
        "hubspot": "hubspot.get_company",
        "salesforce": "salesforce.get_account",
    },
    "crm.company.update": {
        "hubspot": "hubspot.update_company",
        "salesforce": "salesforce.update_account",
    },
    "crm.company.search": {
        "hubspot": "hubspot.search_companies",
        "salesforce": "salesforce.search_accounts",
    },
}


@dataclass(frozen=True)
class CRMCompanyPublicResult:
    capability: str
    status: str = "rejected"
    company: dict[str, Any] | None = None
    items: list[dict[str, Any]] | None = None
    next_cursor: str | None = None
    reason_code: str | None = None
    message: str | None = None

    def as_dict(self) -> dict[str, Any]:
        body: dict[str, Any] = {
            "capability": self.capability,
            "status": self.status,
        }
        if self.company is not None:
            body["company"] = dict(self.company)
        if self.items is not None:
            body["items"] = list(self.items)
        if self.next_cursor is not None:
            body["next_cursor"] = self.next_cursor
        if self.reason_code is not None:
            body["reason_code"] = self.reason_code
        if self.message is not None:
            body["message"] = self.message
        return body


def _fail(
    code: str, message: str, diagnostics: dict[str, Any] | None = None
) -> CRMResolution:
    return fail_crm(code, message, diagnostics)


def _catalog_invocable(capability: str) -> bool:
    from src.domain.connectors.invoke_capabilities import capability_surface

    return bool(capability_surface(capability, []).get("invocable"))


def _company_input(raw: Any) -> CRMCompanyInput | str:
    if not isinstance(raw, dict):
        return "crm_validation_failed"
    try:
        return CRMCompanyInput.model_validate(raw)
    except (ValidationError, CRMCanonicalError):
        return "crm_property_invalid"


def _company_ref(raw: Any) -> CRMObjectRef | str:
    if isinstance(raw, str) or not isinstance(raw, dict):
        return "crm_validation_failed"
    if raw.get("object_type") != "company":
        return "crm_validation_failed"
    try:
        return CRMObjectRef.model_validate(raw)
    except ValidationError:
        return "crm_validation_failed"


def map_company_properties(dumped: dict[str, Any], provider: str) -> dict[str, Any] | str:
    if provider == "salesforce" and "domain" in dumped:
        return "crm_field_unsupported"
    names = HUBSPOT_COMPANY_FIELDS if provider == "hubspot" else SALESFORCE_COMPANY_FIELDS
    bag: dict[str, Any] = {}
    for canonical, wire in names.items():
        if canonical in dumped:
            bag[wire] = dumped[canonical]
    extra = dumped.get("properties")
    if isinstance(extra, dict):
        bag.update(extra)
    return bag


def map_company_capability_input(
    capability: str, provider: str, payload: dict[str, Any]
) -> dict[str, Any] | str:
    if capability == "crm.company.create":
        parsed = _company_input(payload.get("company"))
        if isinstance(parsed, str):
            return parsed
        dumped = dump_crm_input(parsed)
        props = map_company_properties(dumped, provider)
        if isinstance(props, str):
            return props
        if provider == "hubspot":
            return {"properties": props}
        return {"fields": props}
    if capability == "crm.company.update":
        parsed_ref = _company_ref(payload.get("company"))
        if isinstance(parsed_ref, str):
            return parsed_ref
        parsed = _company_input(payload.get("changes") or {})
        if isinstance(parsed, str):
            return parsed
        dumped = dump_crm_input(parsed)
        props = map_company_properties(dumped, provider)
        if isinstance(props, str):
            return props
        if provider == "hubspot":
            return {
                "company_id": parsed_ref.external_id,
                "properties": props,
            }
        return {"account_id": parsed_ref.external_id, "fields": props}
    if capability == "crm.company.get":
        parsed_ref = _company_ref(payload.get("company"))
        if isinstance(parsed_ref, str):
            return parsed_ref
        if provider == "hubspot":
            return {
                "company_id": parsed_ref.external_id,
                "properties": ["name", "domain", "phone"],
            }
        return {
            "account_id": parsed_ref.external_id,
            "fields": ["Id", "Name", "Phone"],
        }
    if capability == "crm.company.search":
        if any(key in payload for key in ("domain", "website", "Website")):
            return "crm_field_unsupported"
        name = payload.get("name")
        if not isinstance(name, str) or not name.strip():
            return "crm_validation_failed"
        extra = payload.keys() - {"name", "cursor", "metadata", "provider_hint"}
        if extra:
            return "crm_validation_failed"
        if provider == "hubspot":
            mapped: dict[str, Any] = {
                "filter_groups": [
                    {
                        "filters": [
                            {
                                "propertyName": "name",
                                "operator": "EQ",
                                "value": name.strip(),
                            }
                        ]
                    }
                ],
                "properties": ["name", "domain", "phone"],
                "limit": 10,
            }
            cursor = payload.get("cursor")
            if isinstance(cursor, str) and cursor.strip():
                after = decode_crm_cursor("hubspot", cursor.strip())
                if after is None:
                    return "crm_validation_failed"
                mapped["after"] = after
            return mapped
        if payload.get("cursor"):
            return "crm_validation_failed"
        return {
            "filters": {"Name": name.strip()},
            "fields": ["Id", "Name", "Phone"],
            "limit": 10,
        }
    return "crm_validation_failed"


def normalize_provider_company(provider: str, raw: dict[str, Any]) -> dict[str, Any] | None:
    if provider == "hubspot":
        external = raw.get("id")
        props = raw.get("properties") if isinstance(raw.get("properties"), dict) else {}
        if not isinstance(external, str) or not external:
            return None
        return {
            "ref": {"object_type": "company", "external_id": str(external)},
            "name": props.get("name"),
            "domain": props.get("domain"),
            "phone": props.get("phone"),
        }
    external = raw.get("Id") or raw.get("id")
    if not isinstance(external, str) or not external:
        return None
    return {
        "ref": {"object_type": "company", "external_id": str(external)},
        "name": raw.get("Name") or raw.get("name"),
        "phone": raw.get("Phone") or raw.get("phone"),
    }


def resolve_crm_company(
    *,
    capability: str,
    payload: dict[str, Any],
    connections: list[CRMConnectionSnapshot],
    enabled_capabilities: set[str] | None = None,
    provider_hint: str | None = None,
    binding_connection_id: UUID | None = None,
    approval_present: bool = False,
    enforce_invocable: bool = False,
) -> CRMResolution:
    if capability not in CRM_COMPANY_FROZEN_VERBS:
        return _fail("capability_not_bound", f"Unknown capability {capability}")
    if enforce_invocable and not _catalog_invocable(capability):
        from src.domain.connectors.invoke_capabilities import INVOCABLE_FALSE_MEANS

        return _fail("capability_not_invocable", INVOCABLE_FALSE_MEANS)

    leaked = sorted(
        key
        for key in payload
        if key
        in {
            "company_id",
            "account_id",
            "Id",
            "website",
            "Website",
            "filter_groups",
            "query",
            "soql",
            "after",
            "nextRecordsUrl",
            "queryLocator",
            "external_id_field",
            "lead_id",
        }
    )
    if leaked:
        return _fail(
            "provider_fields_forbidden",
            "crm.company.* does not accept provider API fields",
            {"forbidden_fields": leaked},
        )

    selected = select_crm_connection(
        capability=capability,
        connections=connections,
        action_by_capability=ACTION_BY_CAPABILITY,
        enabled_capabilities=enabled_capabilities,
        provider_hint=provider_hint,
        binding_connection_id=binding_connection_id,
        approval_present=approval_present,
    )
    if not selected.ok or selected.plan is None:
        return selected
    mapped = map_company_capability_input(
        capability, selected.plan.provider, payload
    )
    if isinstance(mapped, str):
        return _fail(mapped, f"{capability} input is invalid", selected.diagnostics)
    notes = {
        **selected.diagnostics,
        "execution_domain": "C",
        "salesforce_lead_excluded": True,
        "salesforce_website_is_not_domain": True,
        "upsert_classification": CRM_COMPANY_UPSERT_CLASSIFICATION,
        "hubspot_null_clear": (
            HUBSPOT_NULL_CLEAR if selected.plan.provider == "hubspot" else None
        ),
    }
    return CRMResolution(
        ok=True,
        plan=CRMPlan(
            capability=capability,
            connection_id=selected.plan.connection_id,
            provider=selected.plan.provider,
            action=selected.plan.action,
            resolved_input=mapped,
            notes={k: v for k, v in notes.items() if v is not None},
        ),
        diagnostics=notes,
    )


def dispatch_crm_company_plan(
    plan: CRMPlan,
    authorization: DispatchAuthorization,
    *,
    diagnostics: dict[str, Any] | None = None,
) -> CRMResolution:
    return dispatch_crm_plan(plan, authorization, diagnostics=diagnostics)


def to_public_crm_company_result(
    resolution: CRMResolution,
    *,
    capability: str,
    enqueue: dict[str, Any] | None = None,
) -> CRMCompanyPublicResult:
    if not resolution.ok:
        return CRMCompanyPublicResult(
            capability=capability,
            status="rejected",
            reason_code=resolution.reason_code,
            message=resolution.message,
        )
    if enqueue is None:
        return CRMCompanyPublicResult(
            capability=capability,
            status="rejected",
            reason_code="crm_provider_unavailable",
            message="crm.company.* has no enqueue path",
        )
    if not enqueue.get("ok"):
        code = str(enqueue.get("reason_code") or "crm_provider_unavailable")
        return CRMCompanyPublicResult(
            capability=capability,
            status="rejected",
            reason_code=map_enqueue_error(code, capability=capability),
            message=str(enqueue.get("message") or "integration.execute enqueue failed"),
        )
    output = enqueue_output(enqueue)
    if output is None:
        return CRMCompanyPublicResult(capability=capability, status="queued")
    if output.get("already_exists") is True and capability == "crm.company.create":
        return CRMCompanyPublicResult(
            capability=capability,
            status="rejected",
            reason_code="crm_duplicate_match",
            message="Company already exists; create does not upsert",
        )
    provider = resolution.plan.provider if resolution.plan else ""
    if capability == "crm.company.search":
        rows = output.get("results") or output.get("records") or []
        items = []
        if isinstance(rows, list):
            for row in rows:
                if isinstance(row, dict):
                    item = normalize_provider_company(provider, row)
                    if item:
                        items.append(item)
        paging = output.get("paging")
        cursor = None
        if isinstance(paging, dict):
            nxt = paging.get("next")
            if isinstance(nxt, dict):
                cursor = nxt.get("after")
        opaque = (
            encode_crm_cursor(provider, str(cursor))
            if cursor and provider == "hubspot"
            else None
        )
        return CRMCompanyPublicResult(
            capability=capability,
            status="succeeded",
            items=items,
            next_cursor=opaque,
        )
    company = normalize_provider_company(provider, output)
    if company is None:
        return CRMCompanyPublicResult(
            capability=capability,
            status="rejected",
            reason_code="crm_company_not_found",
            message="Provider result did not include a company identity",
        )
    return CRMCompanyPublicResult(
        capability=capability, status="succeeded", company=company
    )
