"""Tests for referral pydantic models."""

from __future__ import annotations

from datetime import datetime, timezone
from uuid import uuid4

import pytest
from pydantic import ValidationError

from referral.models import (
    ApplyReferralRequest,
    CompleteReferralRequest,
    CreateCodeRequest,
    LeaderboardEntry,
    Referral,
    ReferralCode,
    ReferralConfig,
    ReferralReward,
    ReferralStats,
    ReferralStatus,
    RewardStatus,
    RewardType,
)


# ---------------------------------------------------------------------------
# ReferralConfig
# ---------------------------------------------------------------------------


class TestReferralConfig:
    def test_default_config(self) -> None:
        config = ReferralConfig()
        assert config.reward_type == RewardType.CREDIT
        assert config.reward_amount == 10.0
        assert config.max_referrals_per_user == 50
        assert config.default_code_max_uses == 10
        assert config.expiry_days == 90
        assert config.double_sided is True

    def test_custom_config(self) -> None:
        config = ReferralConfig(
            reward_type=RewardType.DISCOUNT,
            reward_amount=25.0,
            max_referrals_per_user=100,
            default_code_max_uses=5,
            expiry_days=30,
            double_sided=False,
        )
        assert config.reward_type == RewardType.DISCOUNT
        assert config.reward_amount == 25.0
        assert config.double_sided is False

    def test_config_serialization_roundtrip(self) -> None:
        config = ReferralConfig(reward_type=RewardType.FEATURE, reward_amount=0)
        data = config.model_dump()
        restored = ReferralConfig(**data)
        assert restored == config


# ---------------------------------------------------------------------------
# ReferralCode
# ---------------------------------------------------------------------------


class TestReferralCode:
    def test_create_code(self) -> None:
        code = ReferralCode(
            id=uuid4(),
            code="ABCD1234",
            user_id="user-1",
            created_at=datetime.now(timezone.utc),
        )
        assert code.uses == 0
        assert code.active is True
        assert code.max_uses is None

    def test_code_min_length_validation(self) -> None:
        with pytest.raises(ValidationError):
            ReferralCode(
                id=uuid4(),
                code="",
                user_id="user-1",
                created_at=datetime.now(timezone.utc),
            )

    def test_code_with_max_uses(self) -> None:
        code = ReferralCode(
            id=uuid4(),
            code="CODE5678",
            user_id="user-2",
            max_uses=5,
            created_at=datetime.now(timezone.utc),
        )
        assert code.max_uses == 5

    def test_code_with_expiry(self) -> None:
        now = datetime.now(timezone.utc)
        code = ReferralCode(
            id=uuid4(),
            code="EXPIRY01",
            user_id="user-3",
            created_at=now,
            expires_at=now,
        )
        assert code.expires_at == now


# ---------------------------------------------------------------------------
# Referral
# ---------------------------------------------------------------------------


class TestReferral:
    def test_create_referral(self) -> None:
        ref = Referral(
            id=uuid4(),
            referrer_id="user-1",
            referred_id="user-2",
            code="ABCD1234",
            created_at=datetime.now(timezone.utc),
        )
        assert ref.status == ReferralStatus.PENDING
        assert ref.completed_at is None
        assert ref.metadata == {}

    def test_referral_all_statuses(self) -> None:
        for status in ReferralStatus:
            ref = Referral(
                id=uuid4(),
                referrer_id="u1",
                referred_id="u2",
                code="X",
                status=status,
                created_at=datetime.now(timezone.utc),
            )
            assert ref.status == status

    def test_referral_with_metadata(self) -> None:
        ref = Referral(
            id=uuid4(),
            referrer_id="u1",
            referred_id="u2",
            code="META",
            created_at=datetime.now(timezone.utc),
            metadata={"source": "email", "campaign": "spring2025"},
        )
        assert ref.metadata["source"] == "email"


# ---------------------------------------------------------------------------
# ReferralReward
# ---------------------------------------------------------------------------


class TestReferralReward:
    def test_create_reward(self) -> None:
        reward = ReferralReward(
            id=uuid4(),
            user_id="user-1",
            referral_id=uuid4(),
            reward_type=RewardType.CREDIT,
            amount=10.0,
        )
        assert reward.status == RewardStatus.PENDING
        assert reward.granted_at is None

    def test_reward_types(self) -> None:
        for rt in RewardType:
            reward = ReferralReward(
                id=uuid4(),
                user_id="u1",
                referral_id=uuid4(),
                reward_type=rt,
                amount=1.0,
            )
            assert reward.reward_type == rt

    def test_reward_statuses(self) -> None:
        for rs in RewardStatus:
            reward = ReferralReward(
                id=uuid4(),
                user_id="u1",
                referral_id=uuid4(),
                reward_type=RewardType.CREDIT,
                amount=1.0,
                status=rs,
            )
            assert reward.status == rs


# ---------------------------------------------------------------------------
# ReferralStats
# ---------------------------------------------------------------------------


class TestReferralStats:
    def test_default_stats(self) -> None:
        stats = ReferralStats(user_id="user-1")
        assert stats.total_referrals == 0
        assert stats.completed == 0
        assert stats.pending == 0
        assert stats.expired == 0
        assert stats.total_rewards_earned == 0.0

    def test_populated_stats(self) -> None:
        stats = ReferralStats(
            user_id="user-1",
            total_referrals=10,
            completed=7,
            pending=2,
            expired=1,
            total_rewards_earned=70.0,
        )
        assert stats.total_referrals == 10
        assert stats.completed == 7


# ---------------------------------------------------------------------------
# LeaderboardEntry
# ---------------------------------------------------------------------------


class TestLeaderboardEntry:
    def test_create_entry(self) -> None:
        entry = LeaderboardEntry(
            user_id="top-referrer",
            completed_referrals=42,
            total_rewards=420.0,
        )
        assert entry.user_id == "top-referrer"
        assert entry.completed_referrals == 42


# ---------------------------------------------------------------------------
# Request models
# ---------------------------------------------------------------------------


class TestRequestModels:
    def test_create_code_request(self) -> None:
        req = CreateCodeRequest(user_id="user-1")
        assert req.max_uses is None

    def test_create_code_request_with_max_uses(self) -> None:
        req = CreateCodeRequest(user_id="user-1", max_uses=5)
        assert req.max_uses == 5

    def test_apply_referral_request(self) -> None:
        req = ApplyReferralRequest(code="ABC123", referred_user_id="user-2")
        assert req.code == "ABC123"

    def test_complete_referral_request(self) -> None:
        uid = uuid4()
        req = CompleteReferralRequest(referral_id=uid)
        assert req.referral_id == uid


# ---------------------------------------------------------------------------
# Enum string values
# ---------------------------------------------------------------------------


class TestEnumValues:
    def test_referral_status_values(self) -> None:
        assert ReferralStatus.PENDING.value == "pending"
        assert ReferralStatus.COMPLETED.value == "completed"
        assert ReferralStatus.REWARDED.value == "rewarded"
        assert ReferralStatus.EXPIRED.value == "expired"

    def test_reward_type_values(self) -> None:
        assert RewardType.CREDIT.value == "credit"
        assert RewardType.DISCOUNT.value == "discount"
        assert RewardType.FEATURE.value == "feature"
        assert RewardType.CUSTOM.value == "custom"

    def test_reward_status_values(self) -> None:
        assert RewardStatus.PENDING.value == "pending"
        assert RewardStatus.GRANTED.value == "granted"
        assert RewardStatus.FAILED.value == "failed"
