import datetime
import uuid
from decimal import Decimal

import pytest
from freezegun import freeze_time
from src.domain.entities.quota_allocation import QuotaAllocation, QuotaAllocationStatus, QuotaAllocationType


class TestQuotaAllocation:
    class TestSerialization:
        def test(self):
            """QuotaAllocationのシリアライズが正しいことを確認する"""
            expected_type = "base"
            expected_status = "active"
            expected_amount = 10000
            expected_used = 0
            expected_valid_from_date = "2025-01-01"
            expected_valid_to_date = "2025-12-31"

            actual_quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.ACTIVE,
                amount=Decimal(10000),
                used=Decimal(0),
                valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
            )

            serialized = actual_quota_allocation.model_dump()
            assert serialized["type"] == expected_type
            assert serialized["status"] == expected_status
            assert serialized["amount"] == expected_amount
            assert serialized["used"] == expected_used
            assert serialized["valid_from_date"] == expected_valid_from_date
            assert serialized["valid_to_date"] == expected_valid_to_date

    class TestDates:
        def test_invalid_dates(self):
            """有効開始日が有効終了日より未来"""
            with pytest.raises(ValueError, match="valid_from_date must be before valid_to_date"):
                QuotaAllocation(
                    id=uuid.uuid4(),
                    type=QuotaAllocationType.BASE,
                    status=QuotaAllocationStatus.ACTIVE,
                    amount=Decimal(10000),
                    used=Decimal(0),
                    valid_from_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
                    valid_to_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                )

    class TestUsed:
        def test_invalid_used_negative(self):
            """使用量が負の値"""
            with pytest.raises(ValueError, match="used must be a non-negative value"):
                QuotaAllocation(
                    id=uuid.uuid4(),
                    type=QuotaAllocationType.BASE,
                    status=QuotaAllocationStatus.ACTIVE,
                    amount=Decimal(10000),
                    used=Decimal(-1),
                    valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                    valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
                )

        def test_invalid_used_exceed_amount(self):
            """使用量が総量を超過"""
            with pytest.raises(ValueError, match="used must not exceed amount"):
                QuotaAllocation(
                    id=uuid.uuid4(),
                    type=QuotaAllocationType.BASE,
                    status=QuotaAllocationStatus.ACTIVE,
                    amount=Decimal(10000),
                    used=Decimal(10001),
                    valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                    valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
                )

        def test_invalid_used_scheduled(self):
            """SCHEDULEDステータスの使用量が0でない"""
            with pytest.raises(ValueError, match="used must be 0 if status is SCHEDULED"):
                QuotaAllocation(
                    id=uuid.uuid4(),
                    type=QuotaAllocationType.BASE,
                    status=QuotaAllocationStatus.SCHEDULED,
                    amount=Decimal(10000),
                    used=Decimal(1),
                    valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                    valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
                )

    class TestRemainingMonths:
        @freeze_time("2025-09-01 12:00:00")
        def test_remaining_months_cross_year(self):
            """2025年9月1日時点で、2025年6月1日から2026年5月31日までの契約が有効な場合、残り月数は9ヶ月"""
            expected_remaining_months = 9

            actual_quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.ACTIVE,
                amount=Decimal(10000),
                used=Decimal(0),
                valid_from_date=datetime.datetime(year=2025, month=6, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2026, month=5, day=31, tzinfo=datetime.timezone.utc),
            )

            assert actual_quota_allocation.remaining_months == expected_remaining_months

        @freeze_time("2025-09-01 12:00:00")
        def test_remaining_months_not_cross_year(self):
            """2025年9月1日時点で、2025年1月1日から2025年12月31日までの契約が有効な場合、残り月数は4ヶ月"""
            expected_remaining_months = 4

            actual_quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.ACTIVE,
                amount=Decimal(10000),
                used=Decimal(0),
                valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
            )

            assert actual_quota_allocation.remaining_months == expected_remaining_months

        @freeze_time("2025-09-01 12:00:00")
        def test_remaining_months_expired(self):
            """2025年9月1日時点で、2025年1月1日から2025年8月31日までの契約が枯渇の場合、残り月数は4ヶ月"""
            expected_remaining_months = 4

            actual_quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.EXCEEDED,
                amount=Decimal(10000),
                used=Decimal(0),
                valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
            )

            assert actual_quota_allocation.remaining_months == expected_remaining_months

        @freeze_time("2025-09-01 12:00:00")
        def test_remaining_months_half_year_quota_allocation(self):
            """2025年9月1日時点で、2025年6月1日から2025年12月31日までの契約が有効な場合、残り月数は4ヶ月"""
            expected_remaining_months = 4

            actual_quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.ACTIVE,
                amount=Decimal(10000),
                used=Decimal(0),
                valid_from_date=datetime.datetime(year=2025, month=6, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
            )

            assert actual_quota_allocation.remaining_months == expected_remaining_months

    class TestCreate:
        @pytest.mark.parametrize(
            ("frozen_datetime", "valid_from_date", "valid_to_date", "expected_status"),
            [
                (
                    "2024-12-31 00:00:00+09:00",
                    datetime.date(2025, 1, 1),
                    datetime.date(2025, 12, 31),
                    QuotaAllocationStatus.SCHEDULED,
                ),
                (
                    "2025-01-01 00:00:00+09:00",
                    datetime.date(2025, 1, 1),
                    datetime.date(2025, 12, 31),
                    QuotaAllocationStatus.ACTIVE,
                ),
                (
                    "2025-06-01 00:00:00+09:00",
                    datetime.date(2025, 1, 1),
                    datetime.date(2025, 12, 31),
                    QuotaAllocationStatus.ACTIVE,
                ),
                (
                    "2025-12-31 00:00:00+09:00",
                    datetime.date(2025, 1, 1),
                    datetime.date(2025, 12, 31),
                    QuotaAllocationStatus.ACTIVE,
                ),
                (
                    "2026-01-01 00:00:00+09:00",
                    datetime.date(2025, 1, 1),
                    datetime.date(2025, 12, 31),
                    QuotaAllocationStatus.EXPIRED,
                ),
            ],
        )
        def test_create(
            self,
            frozen_datetime: str,
            valid_from_date: datetime.date,
            valid_to_date: datetime.date,
            expected_status: QuotaAllocationStatus,
        ) -> None:
            """現在日と有効期間に応じたステータスが設定されることを確認する"""
            with freeze_time(frozen_datetime):
                # Act
                actual = QuotaAllocation.create(
                    id=uuid.uuid4(),
                    type=QuotaAllocationType.BASE,
                    amount=Decimal(10000),
                    valid_from_date=valid_from_date,
                    valid_to_date=valid_to_date,
                )

            # Assert
            assert actual.status == expected_status

    class TestUse:
        def test_use_with_no_remainder(self):
            """使用量が正しく更新され、余りが返されることを確認する"""
            actual_quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.ACTIVE,
                amount=Decimal(10000),
                used=Decimal(2000),
                valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
            )

            remaining = actual_quota_allocation.use(Decimal(3000))
            assert actual_quota_allocation.used == Decimal(5000)
            assert remaining == Decimal(0)

        def test_use_with_remainder(self):
            """使用量が正しく更新され、余りが返されることを確認する"""
            actual_quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.ACTIVE,
                amount=Decimal(10000),
                used=Decimal(8000),
                valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
            )

            remaining = actual_quota_allocation.use(Decimal(3000))
            assert actual_quota_allocation.used == Decimal(10000)
            assert remaining == Decimal(1000)

        def test_use_inactive_allocation(self):
            """非アクティブなクォータ割り当てに対して使用しようとするとエラー"""
            quota_allocation = QuotaAllocation(
                id=uuid.uuid4(),
                type=QuotaAllocationType.BASE,
                status=QuotaAllocationStatus.EXPIRED,
                amount=Decimal(10000),
                used=Decimal(2000),
                valid_from_date=datetime.datetime(year=2025, month=1, day=1, tzinfo=datetime.timezone.utc),
                valid_to_date=datetime.datetime(year=2025, month=12, day=31, tzinfo=datetime.timezone.utc),
            )

            with pytest.raises(ValueError, match="Cannot use quota allocation that is not active"):
                quota_allocation.use(Decimal(1000))


class TestQuotaAllocationType:
    class TestLabel:
        def test_base_label(self):
            """BASE の label が '基本契約' であることを確認する"""
            assert QuotaAllocationType.BASE.label == "基本契約"

        def test_top_up_label(self):
            """TOP_UP の label が '随時拡張' であることを確認する"""
            assert QuotaAllocationType.TOP_UP.label == "随時拡張"
