"""Anki domain policies."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import date, timedelta
from decimal import Decimal
from typing import Protocol

from .entities import Card
from .enums import CardState, ReviewRating
from .exceptions import InvalidReviewError


@dataclass(frozen=True)
class ReviewPlan:
    reviewed_on: date
    next_state: CardState
    interval_days: int
    ease_factor: Decimal
    repetitions: int
    lapses: int
    due_at: date


class SchedulingPolicy(Protocol):
    def schedule(self, card: Card, rating: ReviewRating, reviewed_on: date | None = None) -> ReviewPlan: ...


@dataclass(frozen=True)
class DefaultSchedulingPolicy:
    minimum_ease_factor: Decimal = Decimal("1.30")

    def schedule(self, card: Card, rating: ReviewRating, reviewed_on: date | None = None) -> ReviewPlan:
        if not isinstance(card, Card):
            raise TypeError("card must be a Card instance")
        if not isinstance(rating, ReviewRating):
            raise InvalidReviewError("rating must be a ReviewRating")
        if card.state == CardState.SUSPENDED:
            raise InvalidReviewError("cannot schedule a suspended card")

        reviewed_on = reviewed_on or date.today()
        if not isinstance(reviewed_on, date):
            raise InvalidReviewError("reviewed_on must be a date")

        base_interval = max(0, card.interval_days)
        base_ease = card.ease_factor
        base_repetitions = card.repetitions
        base_lapses = card.lapses

        if rating == ReviewRating.AGAIN:
            interval_days = 1
            ease_factor = max(self.minimum_ease_factor, base_ease - Decimal("0.20"))
            repetitions = 0
            lapses = base_lapses + 1
            next_state = CardState.LEARNING
        elif rating == ReviewRating.HARD:
            interval_days = max(1, int(Decimal(base_interval or 1) * Decimal("1.20")))
            ease_factor = max(self.minimum_ease_factor, base_ease - Decimal("0.15"))
            repetitions = base_repetitions + 1 if base_repetitions > 0 else 1
            lapses = base_lapses
            next_state = CardState.REVIEW if repetitions > 1 else CardState.LEARNING
        elif rating == ReviewRating.GOOD:
            interval_days = 1 if base_interval == 0 else max(1, base_interval * 2)
            ease_factor = base_ease
            repetitions = base_repetitions + 1 if base_repetitions > 0 else 1
            lapses = base_lapses
            next_state = CardState.REVIEW if repetitions > 1 else CardState.LEARNING
        elif rating == ReviewRating.EASY:
            interval_days = 4 if base_interval == 0 else max(1, int(Decimal(base_interval) * Decimal("2.50")))
            ease_factor = base_ease + Decimal("0.15")
            repetitions = base_repetitions + 1 if base_repetitions > 0 else 1
            lapses = base_lapses
            next_state = CardState.REVIEW if repetitions > 1 else CardState.LEARNING
        else:
            raise InvalidReviewError(f"unsupported review rating: {rating!r}")

        due_at = reviewed_on + timedelta(days=interval_days)
        return ReviewPlan(
            reviewed_on=reviewed_on,
            next_state=next_state,
            interval_days=interval_days,
            ease_factor=ease_factor,
            repetitions=repetitions,
            lapses=lapses,
            due_at=due_at,
        )
