"""
Base class for all subscription types.
Following LDR's pattern from BaseSearchStrategy.
"""

from abc import ABC, abstractmethod
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
from loguru import logger

from ..core.utils import generate_subscription_id, utc_now
from ..core.base_card import CardSource
from .storage import SQLSubscriptionStorage


class BaseSubscription(ABC):
    """Abstract base class for all subscription types."""

    def __init__(
        self,
        user_id: str,
        source: CardSource,
        query_or_topic: str,
        refresh_interval_minutes: int = 240,  # Default 4 hours
        subscription_id: Optional[str] = None,
    ):
        """
        Initialize base subscription.

        Args:
            user_id: ID of the user who owns this subscription
            source: Source information for tracking origin
            query_or_topic: The query or topic to subscribe to
            refresh_interval_minutes: How often to check for updates in minutes
            subscription_id: Optional ID, will generate if not provided
        """
        self.storage = SQLSubscriptionStorage()
        self.id = subscription_id or generate_subscription_id()
        self.user_id = user_id
        self.source = source
        self.query_or_topic = query_or_topic
        self.refresh_interval_minutes = refresh_interval_minutes

        # Timestamps
        self.created_at = utc_now()
        self.last_refreshed = None
        self.next_refresh = self._calculate_next_refresh()

        # Status
        self.is_active = True
        self.refresh_count = 0
        self.error_count = 0
        self.last_error = None

        # Metadata
        self.metadata: Dict[str, Any] = {}

        # Subscription type (to be set by subclasses)
        self.subscription_type = None

    def _calculate_next_refresh(self) -> datetime:
        """Calculate when this subscription should next be refreshed."""
        if self.last_refreshed is None:
            # For new subscriptions, next refresh is created_at + interval
            return self.created_at + timedelta(
                minutes=self.refresh_interval_minutes
            )
        return self.last_refreshed + timedelta(
            minutes=self.refresh_interval_minutes
        )

    def should_refresh(self) -> bool:
        """
        Check if this subscription needs to be refreshed.

        Returns:
            bool: True if refresh is needed
        """
        if not self.is_active:
            return False

        return utc_now() >= self.next_refresh

    def is_due_for_refresh(self) -> bool:
        """Alias for should_refresh for backward compatibility."""
        return self.should_refresh()

    @abstractmethod
    def generate_search_query(self) -> str:
        """
        Generate the search query for this subscription.
        Must be implemented by subclasses.

        Returns:
            str: The search query to execute
        """
        pass

    @abstractmethod
    def get_subscription_type(self) -> str:
        """
        Get the type of this subscription.

        Returns:
            str: Subscription type identifier
        """
        pass

    def on_refresh_start(self) -> None:
        """Called when a refresh begins."""
        logger.debug(f"Starting refresh for subscription {self.id}")
        self.last_refreshed = utc_now()

    def on_refresh_success(self, results: Any) -> None:
        """
        Called when a refresh completes successfully.

        Args:
            results: The results from the refresh
        """
        self.refresh_count += 1
        self.next_refresh = self._calculate_next_refresh()
        self.error_count = 0  # Reset error count on success
        logger.debug(f"Subscription {self.id} refreshed successfully")

    def on_refresh_error(self, error: Exception) -> None:
        """
        Called when a refresh fails.

        Args:
            error: The exception that occurred
        """
        self.error_count += 1
        self.last_error = str(error)

        # Exponential backoff for errors
        backoff_minutes = min(
            self.refresh_interval_minutes * (2**self.error_count),
            24 * 60 * 7,  # Max 1 week in minutes
        )
        self.next_refresh = utc_now() + timedelta(minutes=backoff_minutes)

        logger.error(f"Subscription {self.id} refresh failed: {error}")

        # Disable after too many errors
        if self.error_count >= 10:
            self.is_active = False
            logger.warning(f"Subscription {self.id} disabled after 10 errors")

    def pause(self) -> None:
        """Pause this subscription."""
        self.is_active = False
        logger.info(f"Subscription {self.id} paused")

    def resume(self) -> None:
        """Resume this subscription."""
        self.is_active = True
        self.error_count = 0  # Reset errors on resume
        self.next_refresh = self._calculate_next_refresh()
        logger.info(f"Subscription {self.id} resumed")

    def update_interval(self, new_interval_minutes: int) -> None:
        """
        Update the refresh interval.

        Args:
            new_interval_minutes: New interval in minutes
        """
        if new_interval_minutes < 60:
            raise ValueError(
                "Refresh interval must be at least 60 minutes (1 hour)"
            )
        if new_interval_minutes > 60 * 24 * 30:
            raise ValueError("Refresh interval cannot exceed 30 days")

        self.refresh_interval_minutes = new_interval_minutes
        self.next_refresh = self._calculate_next_refresh()

        logger.info(
            f"Subscription {self.id} interval updated to {new_interval_minutes} minutes"
        )

    def save(self) -> str:
        """
        Save subscription to database.

        Returns:
            str: The subscription ID
        """
        data = {
            "id": self.id,
            "user_id": self.user_id,
            "subscription_type": self.get_subscription_type(),
            "query_or_topic": self.query_or_topic,
            "refresh_interval_minutes": self.refresh_interval_minutes,
            "source_type": self.source.type,
            "source_id": self.source.source_id,
            "created_from": self.source.created_from,
            "is_active": self.is_active,
            "metadata": self.metadata,
            "next_refresh": self.next_refresh,
            "created_at": self.created_at,
        }
        return self.storage.create(data)

    def mark_refreshed(self, results_count: int) -> None:
        """
        Update subscription after successful refresh.

        Args:
            results_count: Number of results found in this refresh
        """
        now = utc_now()
        self.last_refreshed = now
        self.next_refresh = self._calculate_next_refresh()
        self.refresh_count += 1

        # Update in database
        self.storage.update_refresh_time(self.id, now, self.next_refresh)
        self.storage.increment_stats(self.id, results_count)

        logger.debug(
            f"Subscription {self.id} marked as refreshed with {results_count} results"
        )

    def to_dict(self) -> Dict[str, Any]:
        """Convert subscription to dictionary representation."""
        return {
            "id": self.id,
            "type": self.get_subscription_type(),
            "user_id": self.user_id,
            "query_or_topic": self.query_or_topic,
            "source": {
                "type": self.source.type,
                "source_id": self.source.source_id,
                "created_from": self.source.created_from,
                "metadata": self.source.metadata,
            },
            "created_at": self.created_at.isoformat(),
            "last_refreshed": self.last_refreshed.isoformat()
            if self.last_refreshed
            else None,
            "next_refresh": self.next_refresh.isoformat(),
            "refresh_interval_minutes": self.refresh_interval_minutes,
            "is_active": self.is_active,
            "refresh_count": self.refresh_count,
            "error_count": self.error_count,
            "last_error": self.last_error,
            "metadata": self.metadata,
        }
