import json
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch

from bot.services.panel_api_service import PanelApiService
from bot.services.subscription_service import SubscriptionService
from config.settings import Settings


def _settings(tmpdir: str) -> Settings:
    payload = {
        "default_tariff": "basic",
        "tariffs": [
            {
                "key": "basic",
                "names": {"en": "Basic"},
                "descriptions": {"en": "Basic"},
                "squad_uuids": ["basic"],
                "billing_model": "period",
                "monthly_gb": 100,
                "prices_rub": {"1": 100},
                "enabled_periods": [1],
                "hwid_device_limit": 3,
                "enabled": True,
            },
            {
                "key": "pro",
                "names": {"en": "Pro"},
                "descriptions": {"en": "Pro"},
                "squad_uuids": ["pro"],
                "billing_model": "period",
                "monthly_gb": 200,
                "prices_rub": {"1": 200},
                "enabled_periods": [1],
                "hwid_device_limit": 5,
                "enabled": True,
            },
        ],
    }
    config_path = Path(tmpdir) / "tariffs.json"
    config_path.write_text(json.dumps(payload), encoding="utf-8")
    return Settings(
        _env_file=None,
        BOT_TOKEN="token",
        POSTGRES_USER="u",
        POSTGRES_PASSWORD="p",
        TARIFFS_CONFIG_PATH=str(config_path),
    )


def _service(settings: Settings) -> SubscriptionService:
    panel = AsyncMock(spec=PanelApiService)
    panel.update_user_details_on_panel = AsyncMock(return_value={"ok": True})
    return SubscriptionService(settings, panel)


class HwidTariffSwitchConversionTests(unittest.IsolatedAsyncioTestCase):
    async def test_hwid_remaining_rub_value_is_converted_to_target_tariff_days(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            settings = _settings(tmpdir)
            service = _service(settings)
            now = datetime(2099, 1, 15, tzinfo=timezone.utc)
            sub = SimpleNamespace(subscription_id=11)

            with patch(
                "bot.services.subscription_service_impl.tariffs.tariff_dal.get_hwid_device_value_entries",
                AsyncMock(
                    return_value=[
                        {
                            "purchase_id": 7,
                            "purchased_devices": 1,
                            "valid_from": now - timedelta(days=15),
                            "valid_until": now + timedelta(days=15),
                            "created_at": now - timedelta(days=15),
                            "amount": 100,
                            "currency": "RUB",
                        }
                    ]
                ),
            ):
                credit = await service._hwid_conversion_credit(
                    AsyncMock(),
                    sub,
                    at=now,
                )

        self.assertEqual(credit["purchase_ids"], [7])
        self.assertAlmostEqual(credit["value_rub"], 50)

    async def test_switch_expires_converted_hwid_purchases_and_audits_value(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            settings = _settings(tmpdir)
            service = _service(settings)
            user = SimpleNamespace(
                user_id=42,
                telegram_id=42,
                panel_user_uuid="panel-user",
                email=None,
                username="u",
                first_name="U",
                last_name="L",
            )
            sub = SimpleNamespace(
                subscription_id=11,
                user_id=42,
                panel_user_uuid="panel-user",
                panel_subscription_uuid="panel-sub",
                tariff_key="basic",
                start_date=datetime(2099, 1, 1, tzinfo=timezone.utc),
                end_date=datetime(2099, 2, 1, tzinfo=timezone.utc),
                effective_monthly_price_rub=100,
                premium_topup_balance_bytes=0,
                premium_topup_used_bytes=0,
                premium_used_bytes=0,
                topup_balance_bytes=0,
                regular_bonus_bytes=0,
                regular_unlimited_override=False,
                traffic_used_bytes=0,
                extra_hwid_devices=1,
                hwid_device_limit=3,
            )
            updated = SimpleNamespace(**{**sub.__dict__, "tariff_key": "pro"})
            updated.hwid_device_limit = 5
            updated.extra_hwid_devices = 0
            updated.traffic_limit_bytes = 200 * (1024**3)
            updated.premium_is_limited = False
            updated.effective_monthly_price_rub = 200

            with (
                patch(
                    "bot.services.subscription_service_impl.lifecycle.user_dal.get_user_by_id",
                    AsyncMock(return_value=user),
                ),
                patch(
                    "bot.services.subscription_service_impl.lifecycle.subscription_dal.get_active_subscription_by_user_id",
                    AsyncMock(return_value=sub),
                ),
                patch.object(
                    service,
                    "calculate_tariff_switch_options_with_hwid",
                    AsyncMock(
                        return_value={
                            "mode": "period_to_period",
                            "remaining_days": 20,
                            "recalc_days": 25,
                            "paid_diff_rub": 0,
                            "target_monthly_rub": 200,
                            "converted_hwid_value_rub": 50,
                            "converted_hwid_days": 7,
                            "convertible_hwid_purchase_ids": [7],
                        }
                    ),
                ),
                patch(
                    "bot.services.subscription_service_impl.lifecycle.tariff_dal.expire_hwid_device_purchases",
                    AsyncMock(return_value=1),
                ) as expire_purchases,
                patch(
                    "bot.services.subscription_service_impl.lifecycle.tariff_dal.sum_active_hwid_devices",
                    AsyncMock(return_value=0),
                ),
                patch(
                    "bot.services.subscription_service_impl.lifecycle.subscription_dal.update_subscription",
                    AsyncMock(return_value=updated),
                ),
                patch(
                    "bot.services.subscription_service_impl.lifecycle.tariff_dal.create_tariff_change",
                    AsyncMock(),
                ) as create_change,
            ):
                result = await service.switch_tariff_without_payment(
                    AsyncMock(),
                    user_id=42,
                    target_tariff_key="pro",
                    mode="recalc_days",
                )

        self.assertEqual(result["tariff_key"], "pro")
        expire_purchases.assert_awaited_once()
        change_payload = create_change.await_args.args[1]
        self.assertEqual(change_payload["converted_hwid_value_rub"], 50)
        self.assertEqual(change_payload["converted_hwid_days"], 7)


if __name__ == "__main__":  # pragma: no cover
    unittest.main()
