"""
HubSpot property validations helpers (formatting rules).
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional

from g_gremlin.base import AuthenticationError, IntegrationError, RateLimitError, TransientError
from g_gremlin.errors import missing_pyyaml_error
from g_gremlin.hubspot import HubSpotClient
from g_gremlin.hubspot_infra import HubSpotInfraError, _request_with_scope_handling
from g_gremlin.hubspot_schema import HubSpotSchemaCache

VALIDATIONS_BASE_URL = "https://api.hubapi.com/crm/v3/property-validations"

STANDARD_OBJECT_TYPE_IDS = {
    "contacts": "0-1",
    "companies": "0-2",
    "deals": "0-3",
    "tickets": "0-5",
}


class HubSpotValidationsError(HubSpotInfraError):
    """Validation rules API error."""


@dataclass
class ValidationRuleSpec:
    rule_type: str
    rule_arguments: List[str] = field(default_factory=list)
    should_apply_normalization: Optional[bool] = None


@dataclass
class ValidationSpecItem:
    object_type: str
    property_name: str
    rules: List[ValidationRuleSpec]


@dataclass
class ValidationsSpec:
    version: int
    rules: List[ValidationSpecItem]


@dataclass
class ValidationsPlan:
    object_type: str
    property_name: str
    to_create_or_update: List[ValidationRuleSpec] = field(default_factory=list)
    unchanged: List[ValidationRuleSpec] = field(default_factory=list)
    blocked: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "object_type": self.object_type,
            "property_name": self.property_name,
            "create_or_update": [rule_to_dict(r) for r in self.to_create_or_update],
            "unchanged": [rule_to_dict(r) for r in self.unchanged],
            "blocked": self.blocked,
        }


def load_validations_spec(path: Path) -> ValidationsSpec:
    if not path.exists():
        raise HubSpotValidationsError(f"Spec not found: {path}")
    raw_text = path.read_text(encoding="utf-8")
    if path.suffix.lower() in {".yaml", ".yml"}:
        try:
            import yaml  # type: ignore
        except Exception as exc:
            raise missing_pyyaml_error() from exc
        payload = yaml.safe_load(raw_text) or {}
    else:
        payload = json.loads(raw_text)

    version = int(payload.get("version") or 1)
    rules_raw = payload.get("rules") or []
    items: List[ValidationSpecItem] = []
    for rule in rules_raw:
        object_type = rule.get("object_type")
        property_name = rule.get("property_name")
        rule_defs = rule.get("rules") or []
        if not object_type or not property_name:
            raise HubSpotValidationsError("Validation rule missing object_type or property_name.")
        parsed_rules = []
        for r in rule_defs:
            rule_type = r.get("rule_type") or r.get("ruleType")
            if not rule_type:
                raise HubSpotValidationsError("Validation rule missing rule_type.")
            if str(rule_type).lower() == "required":
                raise HubSpotValidationsError(
                    "Required field enforcement is not supported via property validations API. "
                    "For custom objects, use requiredProperties in objects spec."
                )
            parsed_rules.append(
                ValidationRuleSpec(
                    rule_type=str(rule_type),
                    rule_arguments=[str(a) for a in (r.get("rule_arguments") or r.get("ruleArguments") or [])],
                    should_apply_normalization=r.get("should_apply_normalization"),
                )
            )
        items.append(
            ValidationSpecItem(
                object_type=str(object_type),
                property_name=str(property_name),
                rules=parsed_rules,
            )
        )

    return ValidationsSpec(version=version, rules=items)


def plan_validations(client: HubSpotClient, spec: ValidationsSpec) -> List[ValidationsPlan]:
    plans: List[ValidationsPlan] = []
    grouped: Dict[str, List[ValidationSpecItem]] = {}
    for item in spec.rules:
        grouped.setdefault(item.object_type, []).append(item)

    for object_type, items in grouped.items():
        object_type_id = resolve_object_type_id(client, object_type)
        existing = _fetch_validations(client, object_type_id)
        existing_index = {entry["propertyName"]: entry.get("propertyValidationRules") or [] for entry in existing}
        for item in sorted(items, key=lambda r: r.property_name):
            current_rules = existing_index.get(item.property_name, [])
            current_map = {r.get("ruleType"): r for r in current_rules}
            plan = ValidationsPlan(object_type=object_type, property_name=item.property_name)
            for rule in sorted(item.rules, key=lambda r: r.rule_type):
                current = current_map.get(rule.rule_type)
                if not current:
                    plan.to_create_or_update.append(rule)
                    continue
                current_args = [str(a) for a in (current.get("ruleArguments") or [])]
                current_norm = current.get("shouldApplyNormalization")
                if current_args != rule.rule_arguments or (
                    rule.should_apply_normalization is not None and current_norm != rule.should_apply_normalization
                ):
                    plan.to_create_or_update.append(rule)
                else:
                    plan.unchanged.append(rule)
            plans.append(plan)

    return plans


def apply_validations(client: HubSpotClient, plans: List[ValidationsPlan]) -> Dict[str, Any]:
    results = []
    for plan in plans:
        object_type_id = resolve_object_type_id(client, plan.object_type)
        for rule in plan.to_create_or_update:
            _upsert_rule(client, object_type_id, plan.property_name, rule)
        results.append(
            {
                "object_type": plan.object_type,
                "property_name": plan.property_name,
                "updated": len(plan.to_create_or_update),
                "unchanged": len(plan.unchanged),
            }
        )
    return {"results": results}


def resolve_object_type_id(client: HubSpotClient, object_type: str) -> str:
    if object_type in STANDARD_OBJECT_TYPE_IDS:
        return STANDARD_OBJECT_TYPE_IDS[object_type]
    try:
        identity = client.whoami()
        portal_id = identity.get("portal_id") or ""
        if portal_id:
            cache = HubSpotSchemaCache(portal_id)
            if cache.is_stale():
                cache.sync(client)
            for obj in cache.get_objects():
                if obj.name == object_type:
                    schema = _get_schema_by_name(client, object_type)
                    object_type_id = schema.get("objectTypeId") or schema.get("id")
                    if object_type_id:
                        return str(object_type_id)
    except Exception as exc:
        raise HubSpotValidationsError(str(exc)) from exc
    raise HubSpotValidationsError(f"Unknown object type: {object_type}")


def _get_schema_by_name(client: HubSpotClient, object_name: str) -> Dict[str, Any]:
    resp = _request_with_scope_handling(client, "GET", "https://api.hubapi.com/crm/v3/schemas")
    payload = resp.json()
    for schema in payload.get("results", []) or []:
        if schema.get("name") == object_name:
            return schema
    return {}


def _fetch_validations(client: HubSpotClient, object_type_id: str) -> List[Dict[str, Any]]:
    url = f"{VALIDATIONS_BASE_URL}/{object_type_id}"
    try:
        resp = _request_with_scope_handling(client, "GET", url)
        payload = resp.json()
        return payload.get("results", []) or []
    except (AuthenticationError, IntegrationError, RateLimitError, TransientError) as exc:
        raise HubSpotValidationsError(str(exc)) from exc


def _upsert_rule(client: HubSpotClient, object_type_id: str, property_name: str, rule: ValidationRuleSpec) -> None:
    url = f"{VALIDATIONS_BASE_URL}/{object_type_id}/{property_name}/rule-type/{rule.rule_type}"
    body: Dict[str, Any] = {"ruleArguments": rule.rule_arguments}
    if rule.should_apply_normalization is not None:
        body["shouldApplyNormalization"] = rule.should_apply_normalization
    _request_with_scope_handling(client, "PUT", url, json_body=body)


def rule_to_dict(rule: ValidationRuleSpec) -> Dict[str, Any]:
    payload = {
        "rule_type": rule.rule_type,
        "rule_arguments": rule.rule_arguments,
    }
    if rule.should_apply_normalization is not None:
        payload["should_apply_normalization"] = rule.should_apply_normalization
    return payload


def format_validations_plan(plans: List[ValidationsPlan]) -> str:
    lines: List[str] = []
    for plan in plans:
        lines.append(f"{plan.object_type}.{plan.property_name}")
        for rule in plan.to_create_or_update:
            lines.append(f"  + {rule.rule_type} {rule.rule_arguments}")
        for rule in plan.unchanged:
            lines.append(f"  = {rule.rule_type}")
    return "\n".join(lines)
