"""Shared SOQL policy helpers for query-builder and execution paths."""

from __future__ import annotations

import re
from typing import Any, Dict, List, Optional, Set

# Allowlist for safe SOQL queries executed via /api/v2/salesforce/query.
# This keeps the generic query endpoint usable for client features (e.g., dedupe)
# while ensuring only a vetted set of objects/fields can be requested.
SAFE_SOQL_ALLOWLIST: Dict[str, List[str]] = {
    "Lead": [
        "Id",
        "Name",
        "FirstName",
        "LastName",
        "Company",
        "Email",
        "Phone",
        "MobilePhone",
        "Title",
        "City",
        "State",
        "PostalCode",
        "Country",
        "Industry",
        "LeadSource",
        "Website",
        "Status",
        "OwnerId",
        "CreatedDate",
        "LastModifiedDate",
    ],
    "Contact": [
        "Id",
        "Name",
        "FirstName",
        "LastName",
        "Email",
        "Phone",
        "MobilePhone",
        "Title",
        "Department",
        "AccountId",
        "MailingCity",
        "MailingState",
        "MailingCountry",
        "OwnerId",
        "CreatedDate",
        "LastModifiedDate",
    ],
    "Account": [
        "Id",
        "Name",
        "Website",
        "Phone",
        "BillingStreet",
        "BillingCity",
        "BillingState",
        "BillingPostalCode",
        "BillingCountry",
        "Industry",
        "Type",
        "ParentId",
        "OwnerId",
        "CreatedDate",
        "LastModifiedDate",
    ],
    # Related objects referenced via lookups (Owner.Name, etc.)
    "User": [
        "Id",
        "Name",
    ],
}

SAFE_RELATIONSHIP_ALIAS_OBJECTS: Dict[str, str] = {
    "owner": "User",
    "createdby": "User",
    "lastmodifiedby": "User",
    "account": "Account",
    "parent": "Account",
    "contact": "Contact",
}

_SAFE_OBJECT_NAME_MAP = {
    str(object_name or "").strip().lower(): object_name
    for object_name in SAFE_SOQL_ALLOWLIST.keys()
}


def canonical_safe_object_name(object_name: Optional[str]) -> Optional[str]:
    """Return canonical object API name if object is allowlisted, else None."""
    key = str(object_name or "").strip().lower()
    if not key:
        return None
    return _SAFE_OBJECT_NAME_MAP.get(key)


def safe_field_names_for_object(object_name: Optional[str]) -> Optional[Set[str]]:
    """Return lower-cased allowlisted field names for object, else None."""
    canonical_object = canonical_safe_object_name(object_name)
    if not canonical_object:
        return None
    return {
        str(field_name or "").strip().lower()
        for field_name in SAFE_SOQL_ALLOWLIST.get(canonical_object, [])
        if str(field_name or "").strip()
    }


def build_safe_plan_allowlist() -> Dict[str, List[str]]:
    """Return a defensive copy of the static SOQL allowlist."""
    return {obj: fields[:] for obj, fields in SAFE_SOQL_ALLOWLIST.items()}


def safe_relationship_object_for_alias(alias: Optional[str]) -> Optional[str]:
    """Return canonical related object for a relationship alias if known."""
    key = str(alias or "").strip().lower()
    if not key:
        return None
    return SAFE_RELATIONSHIP_ALIAS_OBJECTS.get(key)


def evaluate_safe_query_scope(soql: Optional[str]) -> Dict[str, Any]:
    """Return scoped allowlist info for a query-builder/query execution request."""
    from .salesforce_gateway import parse_soql_touchpoints

    query = str(soql or "").strip()
    touchpoints = parse_soql_touchpoints(query)
    match = re.search(r"\bfrom\s+([a-zA-Z0-9_]+)", query, flags=re.IGNORECASE)
    if not match:
        return {
            "canonical_object": None,
            "plan_allowlist": None,
            "disallowed_fields": [],
            "touchpoints": touchpoints,
        }

    base_object = match.group(1)
    canonical_object = canonical_safe_object_name(base_object)
    if not canonical_object:
        return {
            "canonical_object": None,
            "plan_allowlist": None,
            "disallowed_fields": [],
            "touchpoints": touchpoints,
        }

    touched_fields: Set[str] = set()
    relationship_fields: List[tuple[str, str]] = []
    fields_map = touchpoints.get("fields", {})
    for obj_name, obj_fields in fields_map.items():
        if (obj_name or "").lower() != canonical_object.lower():
            continue
        for raw_field in obj_fields:
            if not raw_field:
                continue
            field_text = str(raw_field).strip()
            if not field_text:
                continue
            if "." in field_text:
                parts = field_text.split(".")
                relationship_alias = parts[0].strip().lower()
                leaf_field = parts[-1].strip().lower()
                if relationship_alias and leaf_field:
                    relationship_fields.append((relationship_alias, leaf_field))
                continue
            touched_fields.add(field_text.lower())

    allowed_fields = safe_field_names_for_object(canonical_object) or set()
    disallowed_fields: Set[str] = {
        field_name
        for field_name in touched_fields
        if field_name and field_name not in allowed_fields
    }

    for relationship_alias, leaf_field in relationship_fields:
        if leaf_field in allowed_fields:
            continue
        related_object = safe_relationship_object_for_alias(relationship_alias)
        if not related_object:
            disallowed_fields.add(f"{relationship_alias}.{leaf_field}")
            continue
        related_allowed = safe_field_names_for_object(related_object) or set()
        if leaf_field not in related_allowed:
            disallowed_fields.add(f"{relationship_alias}.{leaf_field}")

    return {
        "canonical_object": canonical_object,
        "plan_allowlist": build_safe_plan_allowlist(),
        "disallowed_fields": sorted(disallowed_fields),
        "touchpoints": touchpoints,
    }
