"""Helpers for validating Telegram Web App initData and sending messages."""

from __future__ import annotations

import hashlib
import hmac
import json
import logging
import urllib.parse
import urllib.request
from datetime import datetime, timedelta, timezone
from typing import Any, Mapping


logger = logging.getLogger(__name__)


class TelegramDataValidationError(ValueError):
    """Raised when Telegram initData fails validation."""


def parse_init_data(init_data: str) -> dict[str, str]:
    """Parse the initData query string into a flat dict of str -> str."""
    parsed_qs = urllib.parse.parse_qs(init_data, strict_parsing=False)
    return {key: values[0] for key, values in parsed_qs.items()}


def build_data_check_string(parsed_data: Mapping[str, str]) -> str:
    """
    Construct the data_check_string from parsed initData dict.

    This follows Telegram's requirement of alphabetical key sorting and
    excludes the `hash` field.
    """
    return "\n".join(
        f"{key}={parsed_data[key]}"
        for key in sorted(parsed_data.keys())
        if key != "hash"
    )


def calculate_init_data_hash(data_check_string: str, bot_token: str) -> str:
    """Calculate the HMAC-SHA-256 hash for a data_check_string."""
    if not bot_token:
        raise TelegramDataValidationError("Bot token not configured")

    secret_key = hashlib.sha256(bot_token.encode()).digest()
    return hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()


def generate_signed_init_data(
    *,
    user: dict,
    bot_token: str,
    auth_date: int | None = None,
    query_id: str | None = None,
    start_param: str | None = None,
    extra_params: dict[str, str] | None = None,
) -> dict[str, str]:
    """
    Build a signed initData string that mirrors Telegram's format.

    Returns a dict containing:
    - init_data: URL-encoded string ready to use
    - data_check_string: the string that was signed
    - hash: the calculated hash value
    """
    if not bot_token:
        raise TelegramDataValidationError("Bot token not configured")

    params: dict[str, str] = {}
    if extra_params:
        params.update({key: str(value) for key, value in extra_params.items()})

    params.setdefault("user", json.dumps(user))
    params.setdefault(
        "auth_date",
        str(auth_date or int(datetime.now(timezone.utc).timestamp())),
    )

    if query_id:
        params["query_id"] = query_id
    if start_param:
        params["start_param"] = start_param

    data_check_string = build_data_check_string(params)
    params["hash"] = calculate_init_data_hash(data_check_string, bot_token)

    return {
        "init_data": urllib.parse.urlencode(params),
        "data_check_string": data_check_string,
        "hash": params["hash"],
    }


def init_data_debug_snapshot(init_data: str, bot_token: str) -> dict[str, str | None]:
    """
    Provide a debug-friendly view of the initData signature inputs/outputs.

    Useful for development harnesses to compare provided and calculated hashes.
    """
    parsed = parse_init_data(init_data)
    data_check_string = build_data_check_string(parsed)
    try:
        calculated_hash = calculate_init_data_hash(data_check_string, bot_token)
    except TelegramDataValidationError:
        calculated_hash = None

    return {
        "provided_hash": parsed.get("hash"),
        "calculated_hash": calculated_hash,
        "data_check_string": data_check_string,
    }


def validate_telegram_init_data(
    init_data: str,
    bot_token: str,
    expiration_hours: int | None = None,
) -> dict:
    """
    Validate Telegram Web App initData payload.

    Args:
        init_data: Raw initData string from Telegram Web App.
        bot_token: Telegram bot token used to derive the secret key.
        expiration_hours: Optional expiration window in hours. Defaults to 24 hours
                         (Telegram's standard). In dev mode, can be extended via
                         TELEGRAM_INIT_DATA_EXPIRATION_HOURS setting.

    Returns:
        Parsed user data dictionary when validation succeeds.

    Raises:
        TelegramDataValidationError: if validation fails for any reason.
    """
    if not bot_token:
        raise TelegramDataValidationError("Bot token not configured")

    parsed_data = parse_init_data(init_data)

    if "hash" not in parsed_data:
        raise TelegramDataValidationError("Missing hash in init_data")

    received_hash = parsed_data["hash"]
    data_check_string = build_data_check_string(parsed_data)
    calculated_hash = calculate_init_data_hash(data_check_string, bot_token)

    if calculated_hash != received_hash:
        raise TelegramDataValidationError("Invalid hash - data may be tampered")

    # Validate auth_date expiration window.
    # Default to 24 hours (Telegram's standard), but allow override for dev.
    if expiration_hours is None:
        # Try to get from Django settings if available (dev mode only)
        try:
            from django.conf import settings

            expiration_hours = getattr(settings, "TELEGRAM_INIT_DATA_EXPIRATION_HOURS", 24)
        except (ImportError, RuntimeError):
            # Not in Django context or settings not available, use default
            expiration_hours = 24

    if expiration_hours <= 0:
        # Bypass expiration check entirely (dev only)
        pass
    elif "auth_date" in parsed_data:
        auth_date = datetime.fromtimestamp(
            int(parsed_data["auth_date"]),
            tz=timezone.utc,
        )
        age = datetime.now(timezone.utc) - auth_date
        if age > timedelta(hours=expiration_hours):
            raise TelegramDataValidationError(
                f"initData expired (older than {expiration_hours} hours)"
            )

    user_data: dict = {}
    if "user" in parsed_data:
        user_data = json.loads(parsed_data["user"])

    return user_data


def send_telegram_message(
    bot_token: str,
    chat_id: int,
    text: str,
    reply_markup: dict[str, Any] | None = None,
    parse_mode: str | None = "HTML",
) -> bool:
    """
    Send a Telegram message using the Bot API via HTTP.

    This avoids introducing extra async dependencies in the webhook handler
    while still using the official Telegram Bot HTTP endpoint.
    """
    if not bot_token:
        logger.warning("Cannot send Telegram message: bot token missing")
        return False

    payload: dict[str, Any] = {
        "chat_id": chat_id,
        "text": text,
        "disable_web_page_preview": True,
    }

    if parse_mode:
        payload["parse_mode"] = parse_mode
    if reply_markup:
        payload["reply_markup"] = reply_markup

    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    data = json.dumps(payload).encode("utf-8")

    try:
        request = urllib.request.Request(
            url=url,
            data=data,
            method="POST",
            headers={"Content-Type": "application/json"},
        )
        with urllib.request.urlopen(request, timeout=10) as response:
            if response.status != 200:
                logger.warning(
                    "Telegram sendMessage returned non-200 status: %s", response.status
                )
                return False
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning("Failed to send Telegram message: %s", exc)
        return False
