"""Frozen [C] external CRM vocabulary. Not invoke routes and not Cloud entities.

P2HS record keys (`lead`, `contact_submission`, `crm_contact`, `crm_deal`,
`case_record`) are a different execution domain. A local `lead.id` is never a
`CRMObjectRef.external_id`. Salesforce Lead is never `CRMContact`.
"""
from __future__ import annotations

from typing import Any, Literal

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    StrictBool,
    StrictFloat,
    StrictInt,
    StrictStr,
    model_validator,
)

from src.domain.connectors.crm_resources import CRMPipelineRef, CRMStageRef

CRMObjectType = Literal["contact", "company", "deal"]
CRMPropertyValue = StrictStr | StrictInt | StrictFloat | StrictBool | None

P2HS_RECORD_ENTITY_KEYS: tuple[str, ...] = (
    "lead",
    "contact_submission",
    "crm_contact",
    "crm_deal",
    "case_record",
)

CANONICAL_FIELDS: dict[str, tuple[str, ...]] = {
    "contact": ("email", "first_name", "last_name", "phone"),
    "company": ("name", "domain", "phone"),
    "deal": ("name", "amount", "close_date", "pipeline", "stage"),
}

# Adapter transport: HubSpot CRM v3 clears with empty string; Salesforce JSON null.
# Nested objects/arrays are not canonical values.
CRM_PROPERTY_VALUE_TYPES: tuple[str, ...] = ("string", "number", "boolean", "null")
CANONICAL_NULL_MEANS = "clear"
CANONICAL_OMITTED_MEANS = "unchanged_or_unspecified"
CANONICAL_COLLISION = "reject"


class CRMCanonicalError(ValueError):
    def __init__(self, code: str) -> None:
        self.code = code
        super().__init__(code)


def assert_property_map(properties: dict[str, Any] | None, *, object_type: str) -> None:
    if properties is None:
        return
    reserved = set(CANONICAL_FIELDS[object_type])
    overlap = reserved.intersection(properties)
    if overlap:
        raise CRMCanonicalError("canonical_property_collision")
    for value in properties.values():
        if value is None:
            continue
        if isinstance(value, bool) or isinstance(value, (str, int, float)):
            continue
        raise CRMCanonicalError("unsupported_property_value")


CRMPropertyMap = dict[str, CRMPropertyValue]


class CRMObjectRef(BaseModel):
    """Identity in the resolved customer CRM connection only. Not portable across providers."""

    model_config = ConfigDict(extra="forbid")
    object_type: CRMObjectType
    external_id: StrictStr = Field(min_length=1, max_length=128)


class CRMAssociation(BaseModel):
    """Structural link. Relationship type / HubSpot type ids / SF lookup names are not canonical."""

    model_config = ConfigDict(extra="forbid", populate_by_name=True)
    from_ref: CRMObjectRef = Field(alias="from")
    to: CRMObjectRef


class _CRMInput(BaseModel):
    model_config = ConfigDict(extra="forbid")
    properties: dict[str, CRMPropertyValue] | None = None

    @staticmethod
    def object_type() -> str:
        raise NotImplementedError

    @model_validator(mode="after")
    def reject_property_collisions(self):
        assert_property_map(self.properties, object_type=self.object_type())
        return self


class CRMContactInput(_CRMInput):
    email: StrictStr | None = Field(default=None, max_length=320)
    first_name: StrictStr | None = Field(default=None, max_length=120)
    last_name: StrictStr | None = Field(default=None, max_length=120)
    phone: StrictStr | None = Field(default=None, max_length=40)

    @staticmethod
    def object_type() -> str:
        return "contact"

    @model_validator(mode="after")
    def email_not_empty(self):
        if self.email is not None and not self.email.strip():
            raise CRMCanonicalError("invalid_email")
        return self


class CRMContact(CRMContactInput):
    ref: CRMObjectRef


class CRMCompanyInput(_CRMInput):
    name: StrictStr | None = Field(default=None, max_length=255)
    domain: StrictStr | None = Field(default=None, max_length=255)
    phone: StrictStr | None = Field(default=None, max_length=40)

    @staticmethod
    def object_type() -> str:
        return "company"


class CRMCompany(CRMCompanyInput):
    ref: CRMObjectRef


class CRMDealInput(_CRMInput):
    name: StrictStr | None = Field(default=None, max_length=255)
    amount: StrictInt | StrictFloat | None = None
    close_date: StrictStr | None = Field(default=None, max_length=10)
    pipeline: StrictStr | None = Field(default=None, max_length=64)
    stage: StrictStr | None = Field(default=None, max_length=64)

    @staticmethod
    def object_type() -> str:
        return "deal"


class CRMDeal(CRMDealInput):
    ref: CRMObjectRef


def dump_crm_input(model: BaseModel) -> dict[str, Any]:
    """Omitted fields stay absent. Explicit null is included (clear)."""
    return model.model_dump(exclude_unset=True, by_alias=True)


# Mapping: strong means same business meaning, adapter may rename the wire key.
CRM_FIELD_MAP: tuple[dict[str, Any], ...] = (
    {
        "field": "CRMContact.email",
        "meaning": "Primary email; upsert identity when using email upsert",
        "hubspot": "email",
        "salesforce": "Email",
        "strong": True,
        "input": True,
        "result": True,
        "required": "email_upsert_only",
    },
    {
        "field": "CRMContact.first_name",
        "meaning": "Given name",
        "hubspot": "firstname",
        "salesforce": "FirstName",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
    },
    {
        "field": "CRMContact.last_name",
        "meaning": "Family name",
        "hubspot": "lastname",
        "salesforce": "LastName",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
        "notes": "Salesforce Contact create often requires LastName; that is provider validation",
    },
    {
        "field": "CRMContact.phone",
        "meaning": "Primary phone",
        "hubspot": "phone",
        "salesforce": "Phone",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
    },
    {
        "field": "CRMCompany.name",
        "meaning": "Company / account display name",
        "hubspot": "name",
        "salesforce": "Name",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
    },
    {
        "field": "CRMCompany.domain",
        "meaning": "Internet domain host used as HubSpot company upsert identity",
        "hubspot": "domain",
        "salesforce": None,
        "strong": False,
        "input": True,
        "result": True,
        "required": "hubspot_domain_upsert_only",
        "notes": "Salesforce Website is a URL, not a domain; keep it in properties",
    },
    {
        "field": "CRMCompany.phone",
        "meaning": "Primary company phone",
        "hubspot": "phone",
        "salesforce": "Phone",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
    },
    {
        "field": "CRMDeal.name",
        "meaning": "Deal / opportunity name",
        "hubspot": "dealname",
        "salesforce": "Name",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
    },
    {
        "field": "CRMDeal.amount",
        "meaning": "Expected amount in the provider org's deal currency",
        "hubspot": "amount",
        "salesforce": "Amount",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
        "notes": "No canonical currency field; org/portal currency stays provider-specific",
    },
    {
        "field": "CRMDeal.close_date",
        "meaning": "Expected close date YYYY-MM-DD",
        "hubspot": "closedate",
        "salesforce": "CloseDate",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
        "notes": "HubSpot often uses epoch-ms; adapters convert. Not a pipeline resource.",
    },
    {
        "field": "CRMDeal.pipeline",
        "meaning": "Logical pipeline alias resolved on the selected CRM connection",
        "hubspot": "pipeline",
        "salesforce": None,
        "strong": False,
        "input": True,
        "result": True,
        "required": False,
        "notes": (
            "6B froze opaque provider values. 6I Option A: canonical pipeline is a "
            "connection-scoped alias. HubSpot only. Salesforce remains unsupported "
            "(not RecordTypeId / sales process)."
        ),
    },
    {
        "field": "CRMDeal.stage",
        "meaning": "Logical stage alias resolved on the selected CRM connection",
        "hubspot": "dealstage",
        "salesforce": "StageName",
        "strong": True,
        "input": True,
        "result": True,
        "required": False,
        "notes": (
            "6B froze opaque provider values. 6I Option A: canonical stage is a "
            "connection-scoped alias. No raw provider fallback. Unknown read values omitted."
        ),
    },
)

CRM_CANONICAL_MODEL: dict[str, Any] = {
    "slice_id": "6b_crm_canonical_object_model",
    "status": "frozen",
    "execution_domain": "C",
    "not_p2hs_record_entities": P2HS_RECORD_ENTITY_KEYS,
    "frozen_types": (
        "CRMPropertyMap",
        "CRMObjectRef",
        "CRMAssociation",
        "CRMContact",
        "CRMContactInput",
        "CRMCompany",
        "CRMCompanyInput",
        "CRMDeal",
        "CRMDealInput",
        "CRMStageRef",
        "CRMPipelineRef",
    ),
    "deferred_types": {
        # 6F closed Task/Activity; Note stays deferred until both providers have note actions.
        "CRMNote": "defer",
        "CRMTask": "provider_specific_for_now",
        "CRMActivity": "reject_umbrella",
    },
    "object_types": ("contact", "company", "deal"),
    "property_value_types": CRM_PROPERTY_VALUE_TYPES,
    "property_collision": CANONICAL_COLLISION,
    "null_means": CANONICAL_NULL_MEANS,
    "omitted_means": CANONICAL_OMITTED_MEANS,
    "email_required": "email_upsert_only",
    "object_ref_not_portable_across_providers": True,
    "object_ref_is_not_local_record_id": True,
    "salesforce_lead_is_not_crm_contact": True,
    "hubspot_lifecycle_is_not_salesforce_lead": True,
    "crm_lead_app_block_is_p2hs_records": True,
    "get_not_implied_by_object": True,
    "association_relationship": "primary_company_create_frozen_6g",
    "pipeline_stage": "logical_aliases_connection_scoped",
    "field_map": CRM_FIELD_MAP,
    "provider_depth": "canonical_core_plus_provider_actions",
    "hubspot_later": (
        "date_based_api_versions",
        "oauth_v3_for_new_or_certified_apps",
        "legacy_v4_sunset_2027-03-30",
    ),
    "salesforce_later": (
        "rest_sobject_separate_from_inbound_events",
        "change_data_capture",
        "pubsub_api_high_throughput",
    ),
}
