"""HubSpot CRM provider for Scout checks."""

from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List

from g_gremlin.hubspot import HubSpotClient, SEARCH_TOTAL_CAP

from ..crm_provider import Filter, FilterExpr, FilterOp, OrGroup
from ..field_mapping import FieldMapper


class HubSpotProvider:
    name = "hubspot"

    def __init__(
        self,
        profile: str | None = None,
        field_overrides: Dict[str, Dict[str, str]] | None = None,
    ) -> None:
        self.client = HubSpotClient(profile=profile)
        self.mapper = FieldMapper("hubspot", overrides=field_overrides)
        self._warnings: List[str] = []
        self._queries: List[str] = []

    def count(self, object_type: str, filters: List[FilterExpr]) -> int:
        obj_key = str(object_type).lower()
        native_object = self.mapper.native_object(obj_key)
        if not native_object:
            return 0
        filter_groups = self._build_filter_groups(obj_key, filters)
        payload = self.client.search(
            native_object,
            filter_groups=filter_groups,
            properties=[],
            limit=1,
        )
        total = int(payload.get("total", 0) or 0)
        if total >= SEARCH_TOTAL_CAP:
            self._warnings.append(
                "HubSpot search capped at 10k records; counts may be partial."
            )
        self._queries.append(self._format_query(native_object, filter_groups, limit=1))
        return total

    def query(
        self,
        object_type: str,
        fields: List[str],
        filters: List[FilterExpr],
        order_by: str | None = None,
        order_dir: str = "ASC",
        nulls_first: bool = False,
        limit: int = 100,
    ) -> List[Dict[str, Any]]:
        obj_key = str(object_type).lower()
        native_object = self.mapper.native_object(obj_key)
        if not native_object:
            return []
        if obj_key == "task":
            assoc_ids = self._extract_in_filter(filters, "WhoId")
            if assoc_ids:
                return self._query_tasks_for_contacts(assoc_ids, limit=limit)
        native_fields = self._map_query_fields(obj_key, fields)
        filter_groups = self._build_filter_groups(obj_key, filters)
        sorts = []
        if order_by:
            native_order = self.mapper.to_native_field(obj_key, order_by)
            sort_token = f"-{native_order}" if order_dir.upper() == "DESC" else str(native_order)
            sorts.append(sort_token)
        results = self.client.search_all(
            native_object,
            filter_groups=filter_groups,
            properties=native_fields,
            sorts=sorts or None,
            limit=limit,
        )
        self._queries.append(self._format_query(native_object, filter_groups, limit=limit))
        normalized: List[Dict[str, Any]] = []
        for record in results:
            normalized.append(self._normalize_result(obj_key, record))
        return normalized

    def describe(self, object_type: str) -> Dict[str, Any]:
        obj_key = str(object_type).lower()
        native_object = self.mapper.native_object(obj_key) or object_type
        props = self.client.get_properties(native_object)
        return {"object": native_object, "properties": props}

    def update_record(self, object_type: str, record_id: str, values: Dict[str, Any]) -> Dict[str, Any]:
        raise NotImplementedError("HubSpot write actions are not supported yet.")

    def create_record(self, object_type: str, values: Dict[str, Any]) -> Dict[str, Any]:
        raise NotImplementedError("HubSpot write actions are not supported yet.")

    def supports_object(self, object_type: str) -> bool:
        return self.mapper.native_object(str(object_type).lower()) is not None

    def list_owners(self, include_archived: bool = False) -> List[Dict[str, Any]]:
        return self.client.get_owners(include_archived=include_archived)

    def drain_warnings(self) -> List[str]:
        warnings = list(self._warnings)
        self._warnings.clear()
        return warnings

    def drain_queries(self) -> List[str]:
        queries = list(self._queries)
        self._queries.clear()
        return queries

    def _map_query_fields(self, object_type: str, fields: List[str]) -> List[str]:
        native_fields = self.mapper.to_native_fields(object_type, fields)
        if object_type in ("lead", "contact") and "Name" in fields:
            if "firstname" not in native_fields:
                native_fields.append("firstname")
            if "lastname" not in native_fields:
                native_fields.append("lastname")
        # HubSpot doesn't need hs_object_id in properties; id is top-level.
        return [f for f in native_fields if f not in ("hs_object_id", "Id")]

    def _normalize_result(self, object_type: str, record: Dict[str, Any]) -> Dict[str, Any]:
        properties = record.get("properties", {}) or {}
        native_row = dict(properties)
        canonical = self.mapper.from_native_row(object_type, native_row)
        canonical["Id"] = record.get("id")
        if object_type in ("lead", "contact"):
            first = properties.get("firstname") or canonical.get("firstname")
            last = properties.get("lastname") or canonical.get("lastname")
            name_parts = [p for p in [first, last] if p]
            if name_parts:
                canonical["Name"] = " ".join(name_parts).strip()
        return canonical

    def _build_filter_groups(self, object_type: str, filters: List[FilterExpr]) -> List[Dict[str, Any]]:
        groups: List[List[Filter]] = [[]]
        for item in filters:
            if isinstance(item, OrGroup):
                expanded: List[List[Filter]] = []
                for group in groups:
                    for flt in item.filters:
                        expanded.append(group + [flt])
                groups = expanded or groups
            else:
                for group in groups:
                    group.append(item)

        if object_type == "lead":
            for group in groups:
                group.append(Filter("lifecyclestage", FilterOp.EQ, "lead"))

        filter_groups: List[Dict[str, Any]] = []
        for group in groups:
            translated: List[Dict[str, Any]] = []
            for flt in group:
                translated_filter = self._translate_filter(object_type, flt)
                if translated_filter is None:
                    continue
                if isinstance(translated_filter, list):
                    translated.extend(translated_filter)
                else:
                    translated.append(translated_filter)
            if translated:
                filter_groups.append({"filters": translated})
        return filter_groups

    def _translate_filter(self, object_type: str, flt: Filter) -> Dict[str, Any] | List[Dict[str, Any]] | None:
        field = flt.field
        op = flt.op
        value = flt.value

        # Synthetic filters
        if object_type == "lead" and field == "IsConverted":
            if op == FilterOp.EQ:
                if value is True:
                    return self._basic_filter("lifecyclestage", "NEQ", "lead")
                return self._basic_filter("lifecyclestage", "EQ", "lead")
            if op == FilterOp.NEQ:
                if value is True:
                    return self._basic_filter("lifecyclestage", "EQ", "lead")
                return self._basic_filter("lifecyclestage", "NEQ", "lead")

        if object_type == "task" and field in ("WhoId", "WhatId", "RelatedToId"):
            assoc_field = "associations.contact" if field == "WhoId" else "associations.deal"
            return self._translate_simple(assoc_field, op, value)

        native_field = self.mapper.to_native_field(object_type, field)
        return self._translate_simple(native_field, op, value)

    def _extract_in_filter(self, filters: List[FilterExpr], field_name: str) -> List[str]:
        for flt in filters:
            if isinstance(flt, Filter) and flt.field == field_name and flt.op == FilterOp.IN:
                return [str(v) for v in (flt.value or []) if v]
        return []

    def _query_tasks_for_contacts(self, contact_ids: List[str], limit: int = 100) -> List[Dict[str, Any]]:
        rows: List[Dict[str, Any]] = []
        chunk_size = 200
        self._queries.append(f"hubspot.associations:contacts->tasks ids={len(contact_ids)}")
        for i in range(0, len(contact_ids), chunk_size):
            chunk = contact_ids[i : i + chunk_size]
            try:
                mapping = self.client.read_associations_batch("contacts", "tasks", chunk)
            except Exception as exc:
                self._warnings.append(f"HubSpot association lookup failed: {exc}")
                # Fallback: no associations found for this chunk.
                continue
            for contact_id, task_ids in mapping.items():
                for task_id in task_ids:
                    rows.append({"WhoId": contact_id, "RelatedToId": contact_id, "Id": task_id})
                    if limit and len(rows) >= limit:
                        return rows
        return rows

    def _translate_simple(self, native_field: str, op: FilterOp, value: Any) -> Dict[str, Any] | None:
        if op == FilterOp.IS_NULL:
            return {"propertyName": native_field, "operator": "NOT_HAS_PROPERTY"}
        if op == FilterOp.IS_NOT_NULL:
            return {"propertyName": native_field, "operator": "HAS_PROPERTY"}
        if op == FilterOp.BEFORE_TODAY:
            ts = self._today_start_ms()
            return self._basic_filter(native_field, "LT", ts)
        if op == FilterOp.LAST_N_DAYS:
            ts = self._days_ago_ms(int(value))
            return self._basic_filter(native_field, "GTE", ts)
        if op == FilterOp.NOT_LAST_N_DAYS:
            ts = self._days_ago_ms(int(value))
            return self._basic_filter(native_field, "LT", ts)
        if op in (FilterOp.IN, FilterOp.NOT_IN):
            values = value or []
            if not values:
                return None
            operator = "IN" if op == FilterOp.IN else "NOT_IN"
            return {"propertyName": native_field, "operator": operator, "values": list(values)}

        op_map = {
            FilterOp.EQ: "EQ",
            FilterOp.NEQ: "NEQ",
            FilterOp.LT: "LT",
            FilterOp.LTE: "LTE",
            FilterOp.GT: "GT",
            FilterOp.GTE: "GTE",
        }
        if op in op_map:
            return self._basic_filter(native_field, op_map[op], value)
        return None

    def _basic_filter(self, name: str, operator: str, value: Any) -> Dict[str, Any]:
        return {
            "propertyName": name,
            "operator": operator,
            "value": value,
        }

    def _format_query(self, native_object: str, filter_groups: List[Dict[str, Any]], limit: int) -> str:
        return f"hubspot.search:{native_object} limit={limit} filters={len(filter_groups)}"

    def _days_ago_ms(self, days: int) -> int:
        dt = datetime.now(timezone.utc) - timedelta(days=days)
        return int(dt.timestamp() * 1000)

    def _today_start_ms(self) -> int:
        now = datetime.now(timezone.utc)
        start = datetime(year=now.year, month=now.month, day=now.day, tzinfo=timezone.utc)
        return int(start.timestamp() * 1000)


__all__ = ["HubSpotProvider"]
