from datetime import datetime, timezone

from domain.offer import Offer
from ports.booking_repository import BookingRepository


class GetEligibleOffersUseCase:
    def __init__(self, booking_repository: BookingRepository):
        self._booking = booking_repository

    async def execute(self, salon_id: str, client_id: str) -> list[Offer]:
        stats = await self._booking.get_client_stats(client_id)
        raw_offers = await self._booking.list_active_offers(salon_id)
        now = datetime.now(timezone.utc)

        eligible: list[Offer] = []
        for doc in raw_offers:
            if not self._is_eligible(doc, stats, now):
                continue
            eligible.append(
                Offer(
                    id=str(doc["_id"]),
                    title=doc.get("title", ""),
                    description=doc.get("description"),
                )
            )
        return eligible

    @staticmethod
    def _parse_offer_date(value) -> datetime | None:
        if value is None:
            return None
        if isinstance(value, datetime):
            return value if value.tzinfo else value.replace(tzinfo=timezone.utc)
        text = str(value).replace("Z", "+00:00")
        parsed = datetime.fromisoformat(text)
        return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)

    @staticmethod
    def _is_eligible(doc: dict, stats, now: datetime) -> bool:
        offer_type = doc.get("type")
        if offer_type == "festive":
            start_dt = GetEligibleOffersUseCase._parse_offer_date(doc.get("startDate"))
            end_dt = GetEligibleOffersUseCase._parse_offer_date(doc.get("endDate"))
            if start_dt and now < start_dt:
                return False
            if end_dt and now > end_dt:
                return False
            return True

        if offer_type == "nth_visit":
            return stats.appointment_count >= int(doc.get("visitCount") or 0)

        if offer_type == "spend_threshold":
            return stats.total_spend >= float(doc.get("spendThreshold") or 0)

        return False
