from __future__ import annotations

import hashlib
import hmac
import json
import urllib.parse
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch

from django.test import SimpleTestCase

from utils.telegram import (
    TelegramDataValidationError,
    send_telegram_message,
    validate_telegram_init_data,
)


class ValidateTelegramInitDataTests(SimpleTestCase):
    bot_token = "test-bot-token"

    def _build_init_data(self, data: dict[str, str], bot_token: str | None = None) -> str:
        token = bot_token or self.bot_token
        data_check_string = "\n".join(
            f"{key}={value}" for key, value in sorted(data.items())
        )
        secret_key = hashlib.sha256(token.encode()).digest()
        signature = hmac.new(
            secret_key,
            data_check_string.encode(),
            hashlib.sha256,
        ).hexdigest()
        payload = {**data, "hash": signature}
        return urllib.parse.urlencode(payload)

    def test_validate_raises_when_bot_token_missing(self):
        with self.assertRaisesMessage(
            TelegramDataValidationError, "Bot token not configured"
        ):
            validate_telegram_init_data("user=1&hash=abc", bot_token="")

    def test_validate_raises_when_hash_missing(self):
        init_data = urllib.parse.urlencode({"user": json.dumps({"id": 1})})
        with self.assertRaisesMessage(
            TelegramDataValidationError, "Missing hash in init_data"
        ):
            validate_telegram_init_data(init_data, bot_token=self.bot_token)

    def test_validate_raises_on_hash_mismatch(self):
        now = int(datetime.now(tz=timezone.utc).timestamp())
        init_data = self._build_init_data(
            data={
                "auth_date": str(now),
                "user": json.dumps({"id": 1}),
            },
            bot_token="different-token",
        )
        with self.assertRaisesMessage(
            TelegramDataValidationError, "Invalid hash - data may be tampered"
        ):
            validate_telegram_init_data(init_data, bot_token=self.bot_token)

    def test_validate_raises_when_auth_date_expired(self):
        expired = int((datetime.now(tz=timezone.utc) - timedelta(hours=25)).timestamp())
        init_data = self._build_init_data(
            data={
                "auth_date": str(expired),
                "user": json.dumps({"id": 1}),
            }
        )
        with self.assertRaisesMessage(
            TelegramDataValidationError, "initData expired (older than 24 hours)"
        ):
            validate_telegram_init_data(init_data, bot_token=self.bot_token, expiration_hours=24)

    def test_validate_returns_user_payload_when_valid(self):
        user_payload = {"id": 123, "first_name": "Alice", "username": "alice"}
        now = int(datetime.now(tz=timezone.utc).timestamp())
        init_data = self._build_init_data(
            data={
                "auth_date": str(now),
                "user": json.dumps(user_payload),
            }
        )

        result = validate_telegram_init_data(init_data, bot_token=self.bot_token)

        self.assertEqual(result["id"], user_payload["id"])
        self.assertEqual(result["first_name"], user_payload["first_name"])
        self.assertEqual(result["username"], user_payload["username"])


class SendTelegramMessageTests(SimpleTestCase):
    @patch("utils.telegram.urllib.request.urlopen")
    def test_send_message_returns_false_when_token_missing(self, mock_urlopen):
        sent = send_telegram_message("", chat_id=1, text="hello")
        self.assertFalse(sent)
        mock_urlopen.assert_not_called()

    @patch("utils.telegram.urllib.request.urlopen")
    def test_send_message_returns_false_on_non_200_response(self, mock_urlopen):
        mock_response = MagicMock()
        mock_response.__enter__.return_value = mock_response
        mock_response.status = 500
        mock_urlopen.return_value = mock_response

        sent = send_telegram_message("token", chat_id=1, text="hello")

        self.assertFalse(sent)
        mock_urlopen.assert_called_once()

    @patch("utils.telegram.urllib.request.urlopen")
    def test_send_message_returns_true_on_success(self, mock_urlopen):
        mock_response = MagicMock()
        mock_response.__enter__.return_value = mock_response
        mock_response.status = 200
        mock_urlopen.return_value = mock_response

        sent = send_telegram_message("token", chat_id=42, text="welcome")

        self.assertTrue(sent)
        mock_urlopen.assert_called_once()

    @patch("utils.telegram.urllib.request.urlopen")
    def test_send_message_handles_exceptions(self, mock_urlopen):
        mock_urlopen.side_effect = Exception("network error")

        sent = send_telegram_message("token", chat_id=99, text="oops")

        self.assertFalse(sent)
        mock_urlopen.assert_called_once()
