"""
DOOBAT - Tests unitaires TelegramService et SecurityService
Copyright 2026 Vincent Gallet - Vincesoft
"""

import configparser
import sys
import os
import time
import unittest
from unittest.mock import AsyncMock, MagicMock, patch

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from services.security_service import SecurityService
from services.auth_service import AuthService


def make_config(overrides: dict = None) -> configparser.ConfigParser:
    config = configparser.ConfigParser()
    config["TELEGRAM"] = {
        "bot_token": "123456:AAABBBCCC",
        "webhook_secret": "mysecret123",
    }
    config["SECURITY"] = {
        "admin_token": "admintoken456",
        "rate_limit_per_minute": "5",
        "cleanup_days": "7",
    }
    config["DATA"] = {
        "contacts_file": "data/test_contacts.json",
    }
    if overrides:
        for section, values in overrides.items():
            if section not in config:
                config[section] = {}
            config[section].update(values)
    return config


# ── Tests SecurityService ──────────────────────────────────────────────────────

class TestSecurityServiceWebhook(unittest.TestCase):

    def setUp(self):
        self.security = SecurityService(make_config())

    def test_valid_secret(self):
        self.assertTrue(self.security.validate_telegram_secret("mysecret123"))

    def test_invalid_secret(self):
        self.assertFalse(self.security.validate_telegram_secret("wrongsecret"))

    def test_empty_secret(self):
        self.assertFalse(self.security.validate_telegram_secret(""))

    def test_none_secret(self):
        self.assertFalse(self.security.validate_telegram_secret(None))


class TestSecurityServiceRateLimit(unittest.TestCase):

    def setUp(self):
        self.security = SecurityService(make_config())

    def test_within_limit(self):
        for _ in range(5):
            self.assertTrue(self.security.check_rate_limit(12345))

    def test_exceeds_limit(self):
        for _ in range(5):
            self.security.check_rate_limit(99999)
        # La 6ème requête doit être bloquée
        self.assertFalse(self.security.check_rate_limit(99999))

    def test_different_users_independent(self):
        for _ in range(5):
            self.security.check_rate_limit(111)
        # user 222 ne doit pas être affecté par le rate limit de 111
        self.assertTrue(self.security.check_rate_limit(222))


class TestSecurityServiceSanitize(unittest.TestCase):

    def test_sanitize_text_removes_control_chars(self):
        text = "Bonjour\x00\x07 monde"
        result = SecurityService.sanitize_text_input(text)
        self.assertNotIn("\x00", result)
        self.assertNotIn("\x07", result)
        self.assertIn("Bonjour", result)

    def test_sanitize_text_max_length(self):
        long_text = "A" * 1000
        result = SecurityService.sanitize_text_input(long_text, max_length=100)
        self.assertLessEqual(len(result), 100)

    def test_sanitize_phone(self):
        result = SecurityService.sanitize_phone("+33 6 12-34.56 78")
        # Doit garder +, chiffres, espaces, tirets
        self.assertNotIn(".", result)

    def test_sanitize_partner_name_removes_special(self):
        result = SecurityService.sanitize_partner_name("DUPONT<script>alert(1)</script>")
        self.assertNotIn("<", result)
        self.assertNotIn(">", result)

    def test_sanitize_article_name(self):
        result = SecurityService.sanitize_article_name("REF-001/../secret")
        self.assertIn("REF-001", result)

    def test_mask_token(self):
        result = SecurityService.mask_token("123456:AAABBBCCCDDDEEE")
        self.assertIn("...", result)
        self.assertTrue(result.startswith("123456"))

    def test_mask_token_short(self):
        result = SecurityService.mask_token("abc")
        self.assertEqual(result, "***")

    def test_is_valid_chat_id_positive(self):
        self.assertTrue(SecurityService.is_valid_chat_id(123456))

    def test_is_valid_chat_id_negative(self):
        self.assertTrue(SecurityService.is_valid_chat_id(-123456))

    def test_is_valid_chat_id_zero(self):
        self.assertFalse(SecurityService.is_valid_chat_id(0))

    def test_is_valid_partner_id(self):
        self.assertTrue(SecurityService.is_valid_partner_id(1))
        self.assertFalse(SecurityService.is_valid_partner_id(0))
        self.assertFalse(SecurityService.is_valid_partner_id(-1))


# ── Tests AuthService ──────────────────────────────────────────────────────────

class TestAuthService(unittest.TestCase):

    def setUp(self):
        self.config = make_config()
        # Utiliser un fichier temporaire
        import tempfile
        self._tmp = tempfile.NamedTemporaryFile(suffix=".json", delete=False, mode="w")
        self._tmp.write("{}")
        self._tmp.close()
        self.config["DATA"]["contacts_file"] = self._tmp.name
        self.auth = AuthService(self.config)

    def tearDown(self):
        os.unlink(self._tmp.name)

    def test_link_and_find(self):
        self.auth.link_telegram_to_partner(100, 42)
        result = self.auth.find_partner_by_telegram(100)
        self.assertEqual(result, 42)

    def test_find_unknown(self):
        result = self.auth.find_partner_by_telegram(999)
        self.assertIsNone(result)

    def test_unlink(self):
        self.auth.link_telegram_to_partner(200, 55)
        self.auth.unlink_telegram(200)
        self.assertIsNone(self.auth.find_partner_by_telegram(200))

    def test_overwrite_association(self):
        self.auth.link_telegram_to_partner(300, 10)
        self.auth.link_telegram_to_partner(300, 20)
        result = self.auth.find_partner_by_telegram(300)
        self.assertEqual(result, 20)

    def test_link_with_phone(self):
        self.auth.link_telegram_to_partner(350, 30, "+33612345678")
        self.assertEqual(self.auth.find_partner_by_telegram(350), 30)
        self.assertEqual(self.auth.get_linked_phone(350), "+33612345678")

        reloaded = AuthService(self.config)
        self.assertEqual(reloaded.find_partner_by_telegram(350), 30)
        self.assertEqual(reloaded.get_linked_phone(350), "+33612345678")

    def test_list_associations(self):
        self.auth.link_telegram_to_partner(400, 70)
        self.auth.link_telegram_to_partner(500, 80)
        assoc = self.auth.list_associations()
        self.assertIn("400", assoc)
        self.assertIn("500", assoc)

    def test_invalid_types_rejected(self):
        result = self.auth.link_telegram_to_partner("bad", 42)
        self.assertFalse(result)


if __name__ == "__main__":
    unittest.main(verbosity=2)
