from datetime import date, datetime, timezone
from typing import Optional, Any
from sqlmodel import SQLModel
from sqldim.core.fields import Field

class SCD2Mixin(SQLModel):
    """
    Base mixin for SCD Type 2 tracking.
    Default period columns are datetimes. For non-datetime periods (like integer seasons),
    override valid_from/valid_to in the subclass.
    """
    valid_from: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    valid_to: Optional[datetime] = Field(default=None, index=True, nullable=True)
    is_current: bool = Field(default=True, index=True)
    checksum: Optional[str] = Field(default=None, index=True, nullable=True)

class SCD3Mixin(SQLModel):
    """Supports SCD Type 3 (Current + Previous value columns)."""
    # Note: Users define the specific 'previous_X' columns in their model
    # and we handle the rotation logic in SCDHandler.
    pass

class CumulativeMixin(SQLModel):
    """
    Mixin for Cumulative Dimensions/Facts (array-of-structs pattern).
    Provides helpers for managing history arrays.
    """
    def current_value(self, column: str) -> Any:
        """Returns the most recent element in the cumulative array."""
        arr = getattr(self, column, [])
        return arr[-1] if arr else None

    def first_value(self, column: str) -> Any:
        """Returns the first element in the cumulative array."""
        arr = getattr(self, column, [])
        return arr[0] if arr else None

class DatelistMixin(SQLModel):
    """
    Mixin for tracking activity via dates lists.
    Enables bitmask-based analysis (L7, L28).
    """
    def to_bitmask(self, reference_date: date, window: int = 32) -> int:
        """Converts a list of active dates into an integer bitmask."""
        # Implementation based on the provided theoretical requirement
        mask = 0
        dates = getattr(self, "dates_active", [])
        if not dates:
            return mask
            
        for active_date in dates:
            # Handle both date objects and ISO strings (common in JSON serialization)
            if isinstance(active_date, str):
                active_date = date.fromisoformat(active_date)
            diff = (reference_date - active_date).days
            if 0 <= diff < window:
                mask |= (1 << diff)
        return mask

    def activity_in_window(self, days: int, reference_date: date) -> bool:
        """Returns True if any activity occurred within the last N days."""
        mask = self.to_bitmask(reference_date, window=days)
        return mask > 0

    def l7(self, reference_date: date) -> int:
        """Count of active days in the last 7 days."""
        mask = self.to_bitmask(reference_date, window=7)
        return bin(mask).count("1")

    def l28(self, reference_date: date) -> int:
        """Count of active days in the last 28 days."""
        mask = self.to_bitmask(reference_date, window=28)
        return bin(mask).count("1")
