from __future__ import annotations

from datetime import datetime
from uuid import uuid4

import pytest
from sqlalchemy import select

from app.config import Settings
from app.db.models import PaymentOrder, ReferralBonus, ReferralInvite, TelegramUser
from app.services.payment_service import (
    FULFILLED_PAYMENT_STATUS,
    PENDING_PAYMENT_STATUS,
    REFERRAL_BONUS_APPLIED_STATUS,
    REFERRAL_BONUS_PENDING_STATUS,
    REVIEW_PAYMENT_STATUS,
    PaymentService,
)
from app.services.remnawave_client import RemnawaveApiError
from tests.helpers import make_remote_user


class _StubSyncService:
    def __init__(self, *, cached_users_by_telegram: dict[int, list] | None = None) -> None:
        self.refreshed: list[int] = []
        self.cached_users_by_telegram = cached_users_by_telegram or {}

    async def get_referral_summary(self, telegram_id: int):
        return type(
            "ReferralSummaryStub",
            (),
            {
                "applied_referral_code": "",
                "referral_code": "",
                "total_invited": 0,
                "recent_names": [],
            },
        )()

    async def refresh_cached_users_for_telegram(self, *, telegram_id: int):
        self.refreshed.append(telegram_id)
        return []

    async def get_cached_users_for_telegram(self, telegram_id: int):
        return list(self.cached_users_by_telegram.get(telegram_id, []))


class _StubRemnawaveClient:
    def __init__(self, *, created_user, users_by_telegram_id: dict[int, list] | None = None) -> None:
        self.created_user = created_user
        self.created_bodies: list[dict[str, object]] = []
        self.updated_bodies: list[dict[str, object]] = []
        self.users_by_telegram_id = users_by_telegram_id or {}

    async def get_user_by_uuid(self, user_uuid: str):
        for users in self.users_by_telegram_id.values():
            for user in users:
                if str(user.uuid) == user_uuid:
                    return user
        raise RemnawaveApiError(status_code=404, message="not found")

    async def get_user_by_username(self, username: str):
        raise RemnawaveApiError(status_code=404, message="not found")

    async def get_users_by_telegram_id(self, telegram_id: int):
        return list(self.users_by_telegram_id.get(telegram_id, []))

    async def create_user(self, body: dict[str, object]):
        self.created_bodies.append(body)
        return self.created_user

    async def update_user(self, body: dict[str, object]):
        self.updated_bodies.append(body)
        raw_uuid = str(body.get("uuid") or "")
        raw_expire_at = str(body.get("expireAt") or "")
        if raw_expire_at.endswith("Z"):
            raw_expire_at = raw_expire_at.replace("Z", "+00:00")
        next_expire_at = datetime.fromisoformat(raw_expire_at) if raw_expire_at else None

        for telegram_id, users in self.users_by_telegram_id.items():
            for index, user in enumerate(users):
                if str(user.uuid) != raw_uuid:
                    continue
                updated_user = make_remote_user(
                    user_uuid=str(user.uuid),
                    user_id=user.id,
                    short_uuid=user.short_uuid,
                    username=user.username,
                    telegram_id=telegram_id,
                    expire_at=next_expire_at or user.expire_at,
                    traffic_limit_bytes=user.traffic_limit_bytes,
                )
                users[index] = updated_user
                return updated_user

        raise AssertionError("update_user should not be called in this scenario")


async def _seed_order(
    session_factory,
    *,
    telegram_id: int,
    status: str,
    order_uuid: str | None = None,
) -> str:
    resolved_order_uuid = order_uuid or str(uuid4())

    async with session_factory() as session:
        user = TelegramUser(
            telegram_id=telegram_id,
            username="tester",
            first_name="Test",
            language_code="ru",
        )
        session.add(user)
        await session.flush()

        session.add(
            PaymentOrder(
                order_uuid=resolved_order_uuid,
                telegram_user_id=user.id,
                telegram_id=telegram_id,
                plan_code="30d",
                plan_title="OREOL VPN на 30 дней",
                plan_duration_days=30,
                traffic_limit_bytes=0,
                traffic_limit_strategy="NO_RESET",
                amount_stars=250,
                currency="RUB",
                status=status,
                invoice_payload=f"payload-{resolved_order_uuid}",
                provision_username="Oreol-1001-tester",
                error_message=None,
            )
        )
        await session.commit()

    return resolved_order_uuid


async def _seed_referral_invite(
    session_factory,
    *,
    inviter_telegram_id: int,
    invited_telegram_id: int,
) -> None:
    async with session_factory() as session:
        session.add(
            ReferralInvite(
                inviter_telegram_id=inviter_telegram_id,
                invited_telegram_id=invited_telegram_id,
                invited_username="buyer",
                invited_display_name="Buyer",
            )
        )
        await session.commit()


def _make_settings() -> Settings:
    return Settings.model_construct(
        bot_admin_ids_raw="",
        payment_internal_squad_uuids_raw="11111111-1111-1111-1111-111111111111",
        payment_user_tag="BOT",
        payment_external_squad_uuid="",
        payment_username_prefix="Oreol",
        payment_plan_traffic_limit_gb=0,
        payment_plan_traffic_reset_period="NO_RESET",
        referral_discount_percent=5,
        referral_bonus_days=7,
    )


async def test_mark_order_under_review_only_transitions_once(session_factory) -> None:
    service = PaymentService(
        settings=_make_settings(),
        session_factory=session_factory,
        remnawave_client=_StubRemnawaveClient(
            created_user=make_remote_user(
                user_uuid="11111111-1111-1111-1111-111111111111",
                user_id=1,
                short_uuid="abc123",
                username="oreol-user",
                telegram_id=1001,
            )
        ),
        sync_service=_StubSyncService(),
    )
    order_uuid = await _seed_order(
        session_factory,
        telegram_id=1001,
        status=PENDING_PAYMENT_STATUS,
    )

    order = await service.mark_order_under_review(order_uuid=order_uuid, telegram_id=1001)

    assert order.status == REVIEW_PAYMENT_STATUS

    with pytest.raises(ValueError):
        await service.mark_order_under_review(order_uuid=order_uuid, telegram_id=1001)


async def test_approve_order_requires_review_status(session_factory) -> None:
    remote_user = make_remote_user(
        user_uuid="22222222-2222-2222-2222-222222222222",
        user_id=2,
        short_uuid="def456",
        username="oreol-user",
        telegram_id=1001,
    )
    service = PaymentService(
        settings=_make_settings(),
        session_factory=session_factory,
        remnawave_client=_StubRemnawaveClient(created_user=remote_user),
        sync_service=_StubSyncService(),
    )
    order_uuid = await _seed_order(
        session_factory,
        telegram_id=1001,
        status=PENDING_PAYMENT_STATUS,
    )

    with pytest.raises(ValueError):
        await service.approve_order(order_uuid=order_uuid)


async def test_approve_order_fulfills_and_refreshes_cache(session_factory) -> None:
    remote_user = make_remote_user(
        user_uuid="33333333-3333-3333-3333-333333333333",
        user_id=3,
        short_uuid="ghi789",
        username="oreol-user",
        telegram_id=1001,
    )
    sync_service = _StubSyncService()
    remnawave_client = _StubRemnawaveClient(created_user=remote_user)
    service = PaymentService(
        settings=_make_settings(),
        session_factory=session_factory,
        remnawave_client=remnawave_client,
        sync_service=sync_service,
    )
    order_uuid = await _seed_order(
        session_factory,
        telegram_id=1001,
        status=REVIEW_PAYMENT_STATUS,
    )

    access = await service.approve_order(order_uuid=order_uuid)

    assert access.remnawave_user_uuid == str(remote_user.uuid)
    assert access.is_renewal is False
    assert sync_service.refreshed == [1001]
    assert remnawave_client.created_bodies

    async with session_factory() as session:
        order = await session.scalar(
            select(PaymentOrder).where(PaymentOrder.order_uuid == order_uuid)
        )

    assert order is not None
    assert order.status == FULFILLED_PAYMENT_STATUS
    assert order.remnawave_user_uuid == str(remote_user.uuid)


async def test_approve_order_grants_and_applies_referral_bonus(session_factory) -> None:
    buyer_remote_user = make_remote_user(
        user_uuid="44444444-4444-4444-4444-444444444444",
        user_id=4,
        short_uuid="buyer444",
        username="oreol-buyer",
        telegram_id=1001,
    )
    inviter_remote_user = make_remote_user(
        user_uuid="55555555-5555-5555-5555-555555555555",
        user_id=5,
        short_uuid="inviter555",
        username="oreol-inviter",
        telegram_id=2002,
    )
    sync_service = _StubSyncService()
    remnawave_client = _StubRemnawaveClient(
        created_user=buyer_remote_user,
        users_by_telegram_id={2002: [inviter_remote_user]},
    )
    service = PaymentService(
        settings=_make_settings(),
        session_factory=session_factory,
        remnawave_client=remnawave_client,
        sync_service=sync_service,
    )
    order_uuid = await _seed_order(
        session_factory,
        telegram_id=1001,
        status=REVIEW_PAYMENT_STATUS,
    )
    await _seed_referral_invite(
        session_factory,
        inviter_telegram_id=2002,
        invited_telegram_id=1001,
    )

    access = await service.approve_order(order_uuid=order_uuid)

    assert access.referral_bonus is not None
    assert access.referral_bonus.inviter_telegram_id == 2002
    assert access.referral_bonus.bonus_days == 7
    assert access.referral_bonus.status == REFERRAL_BONUS_APPLIED_STATUS
    assert sync_service.refreshed == [1001, 2002]
    assert any(
        body.get("uuid") == str(inviter_remote_user.uuid)
        for body in remnawave_client.updated_bodies
    )

    async with session_factory() as session:
        bonus = await session.scalar(
            select(ReferralBonus).where(ReferralBonus.source_order_uuid == order_uuid)
        )

    assert bonus is not None
    assert bonus.status == REFERRAL_BONUS_APPLIED_STATUS


async def test_approve_order_keeps_referral_bonus_pending_without_inviter_access(
    session_factory,
) -> None:
    buyer_remote_user = make_remote_user(
        user_uuid="66666666-6666-6666-6666-666666666666",
        user_id=6,
        short_uuid="buyer666",
        username="oreol-buyer-2",
        telegram_id=1001,
    )
    sync_service = _StubSyncService()
    remnawave_client = _StubRemnawaveClient(created_user=buyer_remote_user)
    service = PaymentService(
        settings=_make_settings(),
        session_factory=session_factory,
        remnawave_client=remnawave_client,
        sync_service=sync_service,
    )
    order_uuid = await _seed_order(
        session_factory,
        telegram_id=1001,
        status=REVIEW_PAYMENT_STATUS,
    )
    await _seed_referral_invite(
        session_factory,
        inviter_telegram_id=2002,
        invited_telegram_id=1001,
    )

    access = await service.approve_order(order_uuid=order_uuid)

    assert access.referral_bonus is not None
    assert access.referral_bonus.status == REFERRAL_BONUS_PENDING_STATUS
    assert sync_service.refreshed == [1001]

    async with session_factory() as session:
        bonus = await session.scalar(
            select(ReferralBonus).where(ReferralBonus.source_order_uuid == order_uuid)
        )

    assert bonus is not None
    assert bonus.status == REFERRAL_BONUS_PENDING_STATUS


async def test_create_order_generates_unique_provision_username(session_factory) -> None:
    service = PaymentService(
        settings=Settings.model_construct(
            bot_admin_ids_raw="",
            payment_plans_raw="30:250",
            payment_transfer_text="Реквизиты",
            payment_internal_squad_uuids_raw="11111111-1111-1111-1111-111111111111",
            payment_user_tag="BOT",
            payment_external_squad_uuid="",
            payment_username_prefix="Oreol",
            payment_plan_traffic_limit_gb=0,
            payment_plan_traffic_reset_period="NO_RESET",
            referral_discount_percent=5,
            referral_bonus_days=7,
        ),
        session_factory=session_factory,
        remnawave_client=_StubRemnawaveClient(
            created_user=make_remote_user(
                user_uuid="77777777-7777-7777-7777-777777777777",
                user_id=7,
                short_uuid="buyer777",
                username="oreol-user",
                telegram_id=1001,
            )
        ),
        sync_service=_StubSyncService(),
    )

    first_order = await service.create_order(
        telegram_id=1001,
        username="tester",
        first_name="Test",
        last_name=None,
        language_code="ru",
        plan_code="30d",
    )
    second_order = await service.create_order(
        telegram_id=1001,
        username="tester",
        first_name="Test",
        last_name=None,
        language_code="ru",
        plan_code="30d",
    )

    assert first_order.provision_username != second_order.provision_username
    assert first_order.extends_existing_access is False
    assert second_order.extends_existing_access is False


async def test_create_order_transliterates_russian_first_name_without_username(
    session_factory,
) -> None:
    service = PaymentService(
        settings=Settings.model_construct(
            bot_admin_ids_raw="",
            payment_plans_raw="30:250",
            payment_transfer_text="Р РµРєРІРёР·РёС‚С‹",
            payment_internal_squad_uuids_raw="11111111-1111-1111-1111-111111111111",
            payment_user_tag="BOT",
            payment_external_squad_uuid="",
            payment_username_prefix="Oreol",
            payment_plan_traffic_limit_gb=0,
            payment_plan_traffic_reset_period="NO_RESET",
            referral_discount_percent=5,
            referral_bonus_days=7,
        ),
        session_factory=session_factory,
        remnawave_client=_StubRemnawaveClient(
            created_user=make_remote_user(
                user_uuid="99999999-9999-9999-9999-999999999999",
                user_id=9,
                short_uuid="buyer999",
                username="oreol-user",
                telegram_id=1001,
            )
        ),
        sync_service=_StubSyncService(),
    )

    order = await service.create_order(
        telegram_id=1001,
        username=None,
        first_name="\u0410\u043b\u0435\u043a\u0441\u0435\u0439",
        last_name=None,
        language_code="ru",
        plan_code="30d",
    )

    assert order.provision_username.startswith("Oreol-1001-")
    assert order.provision_username.endswith("-Aleksey")
    assert order.provision_username.isascii()


async def test_create_order_marks_existing_access_as_renewal(session_factory) -> None:
    service = PaymentService(
        settings=Settings.model_construct(
            bot_admin_ids_raw="",
            payment_plans_raw="30:250",
            payment_transfer_text="Реквизиты",
            payment_internal_squad_uuids_raw="11111111-1111-1111-1111-111111111111",
            payment_user_tag="BOT",
            payment_external_squad_uuid="",
            payment_username_prefix="Oreol",
            payment_plan_traffic_limit_gb=0,
            payment_plan_traffic_reset_period="NO_RESET",
            referral_discount_percent=5,
            referral_bonus_days=7,
        ),
        session_factory=session_factory,
        remnawave_client=_StubRemnawaveClient(
            created_user=make_remote_user(
                user_uuid="77777777-7777-7777-7777-777777777777",
                user_id=7,
                short_uuid="buyer777",
                username="oreol-user",
                telegram_id=1001,
            )
        ),
        sync_service=_StubSyncService(cached_users_by_telegram={1001: [object()]}),
    )

    order = await service.create_order(
        telegram_id=1001,
        username="tester",
        first_name="Test",
        last_name=None,
        language_code="ru",
        plan_code="30d",
    )

    assert order.extends_existing_access is True
    assert "продлит текущую подписку" in service.build_transfer_text(order=order)


async def test_create_order_targets_external_subscription_linked_by_telegram_id(
    session_factory,
) -> None:
    existing_remote_user = make_remote_user(
        user_uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
        user_id=10,
        short_uuid="external10",
        username="custom-external",
        telegram_id=1001,
    )
    remnawave_client = _StubRemnawaveClient(
        created_user=existing_remote_user,
        users_by_telegram_id={1001: [existing_remote_user]},
    )
    service = PaymentService(
        settings=Settings.model_construct(
            bot_admin_ids_raw="",
            payment_plans_raw="30:250",
            payment_transfer_text="Р РµРєРІРёР·РёС‚С‹",
            payment_internal_squad_uuids_raw="11111111-1111-1111-1111-111111111111",
            payment_user_tag="BOT",
            payment_external_squad_uuid="",
            payment_username_prefix="Oreol",
            payment_plan_traffic_limit_gb=0,
            payment_plan_traffic_reset_period="NO_RESET",
            referral_discount_percent=5,
            referral_bonus_days=7,
        ),
        session_factory=session_factory,
        remnawave_client=remnawave_client,
        sync_service=_StubSyncService(),
    )

    order = await service.create_order(
        telegram_id=1001,
        username="buyer",
        first_name="Buyer",
        last_name=None,
        language_code="ru",
        plan_code="30d",
    )

    assert order.extends_existing_access is True
    assert order.provision_username == "custom-external"

    async with session_factory() as session:
        stored_order = await session.scalar(
            select(PaymentOrder).where(PaymentOrder.order_uuid == order.order_uuid)
        )

    assert stored_order is not None
    assert stored_order.remnawave_user_uuid == str(existing_remote_user.uuid)

    await service.mark_order_under_review(order_uuid=order.order_uuid, telegram_id=1001)
    access = await service.approve_order(order_uuid=order.order_uuid)

    assert access.is_renewal is True
    assert access.username == "custom-external"
    assert remnawave_client.created_bodies == []
    assert remnawave_client.updated_bodies[-1]["username"] == "custom-external"


async def test_approve_order_extends_existing_user_found_by_telegram_id(session_factory) -> None:
    existing_remote_user = make_remote_user(
        user_uuid="88888888-8888-8888-8888-888888888888",
        user_id=8,
        short_uuid="existing888",
        username="oreol-existing",
        telegram_id=1001,
    )
    sync_service = _StubSyncService()
    remnawave_client = _StubRemnawaveClient(
        created_user=existing_remote_user,
        users_by_telegram_id={1001: [existing_remote_user]},
    )
    service = PaymentService(
        settings=_make_settings(),
        session_factory=session_factory,
        remnawave_client=remnawave_client,
        sync_service=sync_service,
    )
    order_uuid = await _seed_order(
        session_factory,
        telegram_id=1001,
        status=REVIEW_PAYMENT_STATUS,
    )

    access = await service.approve_order(order_uuid=order_uuid)

    assert access.remnawave_user_uuid == str(existing_remote_user.uuid)
    assert access.is_renewal is True
    assert remnawave_client.created_bodies == []
    assert remnawave_client.updated_bodies
