import hmac
import logging
from hashlib import sha256
from typing import Any, Mapping

from app.core.config import Settings

logger = logging.getLogger(__name__)


def validate_webhook_signature(
    headers: Mapping[str, str],
    raw_body: bytes,
    settings: Settings,
) -> bool:
    """Placeholder for future X-Freshchat-Signature validation.

    Validation is intentionally disabled until we inspect the real webhook
    headers Freshchat sends for this account.
    """
    if not settings.freshchat_webhook_verify_signature:
        return True

    signature = headers.get("x-freshchat-signature") or headers.get("X-Freshchat-Signature")
    if not signature:
        logger.warning("Freshchat webhook signature verification enabled but signature is missing")
        return False

    # Future implementation: replace FRESHCHAT_API_TOKEN with the actual webhook secret
    # if Freshchat exposes a separate signing secret.
    digest = hmac.new(
        settings.freshchat_api_token.encode("utf-8"),
        raw_body,
        sha256,
    ).hexdigest()
    return hmac.compare_digest(signature, digest)


def extract_event_type(payload: dict[str, Any]) -> str | None:
    return _first_string(
        payload,
        [
            "action",
            "event",
            "event_type",
            "type",
            "data.event",
            "data.event_type",
            "payload.event",
            "payload.event_type",
        ],
    )


def extract_actor_type(payload: dict[str, Any]) -> str | None:
    actor_type = _first_string(
        payload,
        [
            "actor.actor_type",
            "actor.type",
            "actor_type",
            "data.message.actor_type",
            "data.actor.type",
            "sender.type",
            "sender_type",
            "user.type",
            "user_type",
            "customer.type",
            "contact.type",
            "data.actor_type",
            "data.sender.type",
            "data.sender_type",
            "data.message.actor.type",
            "message.actor_type",
            "message.actor.type",
        ],
    )
    return actor_type.lower() if actor_type else None


def extract_actor_id(payload: dict[str, Any]) -> str | None:
    return _first_string(
        payload,
        [
            "actor.id",
            "actor.actor_id",
            "actor.user_id",
            "actor.userId",
            "data.actor.id",
            "data.actor.actor_id",
            "data.actor.user_id",
            "data.actor.userId",
            "sender.id",
            "sender.user_id",
            "data.resolve.resolver_id",
            "data.sender.id",
            "data.sender.user_id",
            "user.id",
            "customer.id",
            "contact.id",
            "data.user.id",
            "data.customer.id",
            "data.contact.id",
            "data.message.actor.id",
            "data.message.actor_id",
            "message.actor.id",
            "message.actor_id",
        ],
    )


def extract_customer_id(payload: dict[str, Any]) -> str | None:
    return _first_string(
        payload,
        [
            "data.message.user_id",
            "data.message.user.id",
            "data.resolve.user.id",
            "data.resolve.conversation.user_id",
            "data.user.id",
            "user.id",
            "customer.id",
            "contact.id",
            "actor.id",
            "actor.actor_id",
        ],
    )


def extract_conversation_id(payload: dict[str, Any]) -> str | None:
    return _first_string(
        payload,
        [
            "data.message.conversation_id",
            "data.conversation_id",
            "conversation_id",
            "conversation.id",
            "conversation.conversation_id",
            "conversationAlias",
            "conversation_alias",
            "conversation.alias",
            "data.conversation.id",
            "data.conversation.conversation_id",
            "data.resolve.conversation.conversation_id",
            "data.resolve.conversation.id",
            "data.conversationAlias",
            "data.conversation_alias",
            "data.conversation.alias",
            "message.conversation_id",
            "payload.conversation_id",
            "payload.conversationAlias",
        ],
    )


def extract_freshchat_conversation_id(payload: dict[str, Any]) -> str | None:
    return _first_string(
        payload,
        [
            "data.message.freshchat_conversation_id",
            "data.resolve.conversation.conversation_internal_id",
            "data.resolve.conversation.freshchat_conversation_id",
        ],
    )


def extract_message_id(payload: dict[str, Any]) -> str | None:
    return _first_string(payload, ["data.message.id", "message.id", "message_id", "messageAlias"])


def extract_channel_id(payload: dict[str, Any]) -> str | None:
    return _first_string(
        payload,
        [
            "data.message.channel_id",
            "message.channel_id",
            "channel_id",
            "data.resolve.conversation.channel_id",
        ],
    )


def extract_message_text(payload: dict[str, Any]) -> str | None:
    message = _first_dict(payload, ["data.message", "message", "payload.message"])
    if message:
        from_parts = _message_parts_text(message)
        if from_parts:
            return from_parts.strip() or None

    direct = _first_string(
        payload,
        [
            "data.message.content",
            "data.message.text",
            "data.message.message",
            "data.message.body",
            "message.content",
            "message.text",
            "message.message",
            "message.body",
            "content",
            "text",
            "body",
            "data.content",
            "data.text",
            "payload.message.text",
            "payload.message.content",
            "payload.text",
        ],
    )
    if direct:
        return direct.strip() or None

    return None


def should_ignore_event(payload: dict[str, Any]) -> bool:
    event_type = (extract_event_type(payload) or "").strip().lower()
    if event_type != "message_create":
        return True

    actor_type = (extract_actor_type(payload) or "").strip().lower()
    if actor_type != "user":
        return True

    message = _first_dict(payload, ["data.message", "message", "payload.message"]) or {}
    if _is_truthy(message.get("botsPrivateNote")):
        return True
    if _is_truthy(message.get("isBotsInput")):
        return True
    if _is_truthy(message.get("restrictResponse")):
        return True

    if not extract_conversation_id(payload):
        return True
    if not extract_message_text(payload):
        return True

    return False


def _message_parts_text(message: dict[str, Any]) -> str | None:
    parts = message.get("message_parts") or message.get("parts") or []
    if not isinstance(parts, list):
        return None

    for part in parts:
        if not isinstance(part, dict):
            continue
        text = part.get("text")
        if isinstance(text, dict) and text.get("content"):
            return str(text["content"]).strip()
        if isinstance(text, str) and text.strip():
            return text.strip()
        quick_reply = part.get("quick_reply_button") or {}
        if isinstance(quick_reply, dict):
            value = quick_reply.get("payload") or quick_reply.get("custom_reply_text") or quick_reply.get("label")
            if value:
                return str(value).strip()
        if _contains_attachment(part):
            return "[archivo recibido]"
    return None


def _contains_attachment(part: dict[str, Any]) -> bool:
    attachment_keys = {
        "image",
        "video",
        "audio",
        "file",
        "attachment",
        "attachments",
        "document",
        "sticker",
    }
    return any(key in part and part.get(key) for key in attachment_keys)


def _first_string(payload: dict[str, Any], paths: list[str]) -> str | None:
    for path in paths:
        value = _get_path(payload, path)
        if value is None:
            continue
        if isinstance(value, (dict, list)):
            continue
        text = str(value).strip()
        if text:
            return text
    return None


def _first_dict(payload: dict[str, Any], paths: list[str]) -> dict[str, Any] | None:
    for path in paths:
        value = _get_path(payload, path)
        if isinstance(value, dict):
            return value
    return None


def _is_truthy(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if value is None:
        return False
    if isinstance(value, (int, float)):
        return value != 0
    if isinstance(value, str):
        return value.strip().lower() in {"true", "1", "yes", "y", "si", "s"}
    return bool(value)


def _get_path(payload: dict[str, Any], path: str) -> Any:
    current: Any = payload
    for key in path.split("."):
        if not isinstance(current, dict):
            return None
        current = current.get(key)
    return current
