"""
DST (Daylight Saving Time) transition handling for real-time data management.

Author: @TexasCoding
Date: 2025-08-22

Overview:
    Provides DST transition detection and handling functionality for real-time market data
    processing. Ensures proper bar alignment and data integrity during timezone transitions
    in trading systems.

Key Features:
    - DST transition detection for any timezone
    - Proper bar alignment during spring forward/fall back transitions
    - Handles missing hours (spring forward) and duplicate hours (fall back)
    - Comprehensive logging for DST-related events
    - Support for both US Eastern (market timezone) and other timezones
    - Future DST transition prediction and preparation

DST Handling Scenarios:
    - Spring Forward: 2:00 AM becomes 3:00 AM (missing hour)
        - Skip bars during the missing hour
        - Prevent creation of invalid timestamps
        - Log the gap in data

    - Fall Back: 2:00 AM becomes 1:00 AM (duplicate hour)
        - Handle first and second occurrence of same hour
        - Use DST-aware timestamps to distinguish
        - Maintain proper bar sequence

    - Cross-DST Data Queries:
        - Proper timezone conversion for historical data
        - Handle gaps and overlaps in bar data
        - Maintain data integrity across transitions

Architecture:
    - Mixin-based design for integration with RealtimeDataManager
    - pytz-based timezone handling for accurate DST detection
    - Event-driven logging for DST events
    - Thread-safe operations with proper locking

Usage:
    This mixin is automatically integrated into RealtimeDataManager when the
    TradingSuite is created. It provides transparent DST handling for all
    real-time data operations.

Example:
    ```python
    # DST handling is automatic with TradingSuite
    suite = await TradingSuite.create(
        "ES",  # S&P 500 E-mini futures
        timeframes=["1min", "5min"],
        timezone="America/Chicago",  # CME timezone with DST
    )

    # DST transitions are handled automatically
    # - Spring forward: No bars created for missing hour
    # - Fall back: Proper handling of duplicate hour
    # - All transitions logged for monitoring
    ```

Performance Considerations:
    - Minimal overhead during normal operation
    - DST transition checks cached for 1-hour periods
    - Only activates special handling during actual transitions
    - Efficient timezone offset calculations

Trading Considerations:
    - Futures markets may have different DST handling than stock markets
    - Some exchanges may not observe DST (e.g., Asia/Tokyo)
    - Critical for intraday strategies around transition times
    - Risk management systems must account for data gaps/overlaps
"""

import logging
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, ClassVar

import pytz

if TYPE_CHECKING:
    from pytz.tzinfo import BaseTzInfo


class DSTHandlingMixin:
    """
    Mixin to handle DST (Daylight Saving Time) transitions in real-time data processing.

    This mixin provides comprehensive DST transition detection and handling for trading
    systems that operate across timezone boundaries. It ensures data integrity during
    spring forward (missing hour) and fall back (duplicate hour) transitions.

    Key Capabilities:
        - Detects upcoming DST transitions within configurable window
        - Handles missing hours during spring forward transitions
        - Manages duplicate hours during fall back transitions
        - Provides DST-aware bar time calculations
        - Logs all DST-related events for monitoring
        - Maintains data integrity across transitions

    Integration:
        This mixin is designed to be included in RealtimeDataManager and provides
        transparent DST handling for all bar creation and timestamp operations.
    """

    # DST transition cache to avoid repeated calculations
    _dst_cache: ClassVar[dict[str, Any]] = {}
    _dst_cache_expiry: ClassVar[dict[str, datetime]] = {}

    # Type declarations for attributes expected from main class
    if TYPE_CHECKING:
        timezone: BaseTzInfo | None

        def _calculate_bar_time(
            self, _timestamp: datetime, _interval: int, _unit: int
        ) -> datetime:
            """Expected method from main class for bar time calculation."""
            ...

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """Initialize DST handling with timezone configuration."""
        super().__init__(*args, **kwargs)

        # DST-specific logger for transition events
        self.dst_logger = logging.getLogger(f"{__name__}.dst")

        # DST configuration
        self._dst_check_window = timedelta(
            hours=6
        )  # Look ahead 6 hours for transitions
        self._dst_log_level = logging.INFO  # Log level for DST events

        # DST state tracking
        self._last_dst_check: datetime | None = None
        self._next_dst_transition: datetime | None = None
        self._in_dst_transition = False

        self.dst_logger.info(
            f"DST handling initialized for timezone: {getattr(self, 'timezone', 'UTC')}"
        )

    def is_dst_transition_period(self, timestamp: datetime) -> bool:
        """
        Check if timestamp falls within a DST transition period.

        Args:
            timestamp: Timestamp to check (should be timezone-aware)

        Returns:
            bool: True if timestamp is during DST transition
        """
        if not hasattr(self, "timezone") or self.timezone is None:
            return False

        tz = self.timezone  # Type checker now knows this is not None

        # Ensure timestamp is timezone-aware
        if timestamp.tzinfo is None:
            timestamp = tz.localize(timestamp)

        # Convert to target timezone if needed
        if timestamp.tzinfo != tz:
            timestamp = timestamp.astimezone(tz)

        # Check cache first (valid for 1 hour)
        cache_key = f"{timestamp.date()}_{timestamp.hour}"
        cache_expiry = self._dst_cache_expiry.get(cache_key)

        if cache_expiry and datetime.now() < cache_expiry:
            cached_result: bool = self._dst_cache.get(cache_key, False)
            return cached_result

        # Perform DST transition check
        is_transition = self._check_dst_transition(timestamp)

        # Cache result for 1 hour
        self._dst_cache[cache_key] = is_transition
        self._dst_cache_expiry[cache_key] = datetime.now() + timedelta(hours=1)

        return is_transition

    def _check_dst_transition(self, timestamp: datetime) -> bool:
        """
        Perform actual DST transition detection.

        Args:
            timestamp: Timezone-aware timestamp to check

        Returns:
            bool: True if during DST transition
        """
        try:
            # Get the date for this timestamp (for potential future use)
            # date = timestamp.date()
            if self.timezone is None:
                return False

            # Check if this timezone observes DST
            if not hasattr(self.timezone, "zone") or self.timezone.zone in [
                "UTC",
                "GMT",
            ]:
                return False

            # Find DST transitions for this year
            transitions = self._get_dst_transitions(timestamp.year)

            for transition_start, transition_end in transitions:
                # Ensure all datetimes are timezone-aware for comparison
                try:
                    if transition_start.tzinfo is None:
                        transition_start = self.timezone.localize(transition_start)
                    if transition_end.tzinfo is None:
                        transition_end = self.timezone.localize(transition_end)
                    if timestamp.tzinfo is None:
                        timestamp = self.timezone.localize(timestamp)

                    if transition_start <= timestamp <= transition_end:
                        return True
                except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError):
                    # During DST transitions, some times may be ambiguous or non-existent
                    # In these cases, we're already in a transition period
                    return True

            return False

        except Exception as e:
            self.dst_logger.warning(f"Error checking DST transition: {e}")
            return False

    def _get_dst_transitions(self, year: int) -> list[tuple[datetime, datetime]]:
        """
        Get DST transition periods for a given year.

        Args:
            year: Year to get transitions for

        Returns:
            list: List of (start, end) tuples for transition periods
        """
        transitions = []

        try:
            # Create datetime objects for the year
            jan1 = datetime(year, 1, 1)
            dec31 = datetime(year, 12, 31, 23, 59, 59)

            # Find all DST transitions in the year
            current = jan1
            last_offset = None

            while current <= dec31:
                try:
                    if self.timezone is None:
                        continue

                    # Localize to timezone and get UTC offset
                    localized = self.timezone.localize(current)
                    current_offset = localized.utcoffset()

                    # Check for offset change (DST transition)
                    if last_offset is not None and current_offset != last_offset:
                        # Found a transition - determine the transition window
                        transition_start = current - timedelta(hours=1)
                        transition_end = current + timedelta(hours=1)
                        transitions.append((transition_start, transition_end))

                        if current_offset is None:
                            continue

                        transition_type = (
                            "Spring Forward"
                            if current_offset > last_offset
                            else "Fall Back"
                        )
                        self.dst_logger.info(
                            f"DST transition detected: {transition_type} at {current} "
                            f"(offset change: {last_offset} -> {current_offset})"
                        )

                    last_offset = current_offset

                except pytz.AmbiguousTimeError:
                    # Fall back - time exists twice
                    transition_start = current - timedelta(hours=1)
                    transition_end = current + timedelta(hours=2)
                    transitions.append((transition_start, transition_end))

                    self.dst_logger.info(f"DST Fall Back detected at {current}")

                except pytz.NonExistentTimeError:
                    # Spring forward - time doesn't exist
                    transition_start = current - timedelta(hours=1)
                    transition_end = current + timedelta(hours=1)
                    transitions.append((transition_start, transition_end))

                    self.dst_logger.info(f"DST Spring Forward detected at {current}")

                # Move to next day
                current += timedelta(days=1)

        except Exception as e:
            self.dst_logger.error(f"Error getting DST transitions for {year}: {e}")

        return transitions

    def handle_dst_bar_time(
        self, timestamp: datetime, interval: int, unit: int
    ) -> datetime | None:
        """
        Calculate bar time with DST transition handling.

        This method provides DST-aware bar time calculations that properly handle
        transitions. During spring forward, it skips non-existent times. During
        fall back, it properly disambiguates duplicate times.

        Args:
            timestamp: Tick timestamp (timezone-aware)
            interval: Bar interval value
            unit: Time unit (1=seconds, 2=minutes)

        Returns:
            datetime: DST-aware bar time, or None if time should be skipped
        """
        if not hasattr(self, "timezone") or self.timezone is None:
            # Fallback to standard calculation - need to check if this method exists
            if hasattr(self, "_calculate_bar_time"):
                return self._calculate_bar_time(timestamp, interval, unit)
            else:
                return timestamp  # Simple fallback

        tz = self.timezone
        # Ensure timestamp is timezone-aware
        if timestamp.tzinfo is None:
            timestamp = tz.localize(timestamp)

        # Check if we're in a DST transition period
        if not self.is_dst_transition_period(timestamp):
            # Normal case - use standard calculation
            return self._calculate_bar_time(timestamp, interval, unit)

        return self._calculate_dst_aware_bar_time(timestamp, interval, unit)

    def _calculate_dst_aware_bar_time(
        self, timestamp: datetime, interval: int, unit: int
    ) -> datetime | None:
        """
        Calculate bar time during DST transitions.

        Args:
            timestamp: Timezone-aware timestamp
            interval: Bar interval value
            unit: Time unit (1=seconds, 2=minutes)

        Returns:
            datetime: Bar time or None if should be skipped
        """
        try:
            # Calculate base bar time using standard method
            base_bar_time = self._calculate_bar_time(timestamp, interval, unit)

            # Check if this bar time is valid during DST transition
            return self._validate_dst_bar_time(base_bar_time)

        except Exception as e:
            self.dst_logger.error(f"Error calculating DST-aware bar time: {e}")
            return None

    def _validate_dst_bar_time(self, bar_time: datetime) -> datetime | None:
        """
        Validate that a bar time is valid during DST transitions.

        Args:
            bar_time: Calculated bar time to validate

        Returns:
            datetime: Valid bar time or None if should be skipped
        """
        try:
            # Check for non-existent time (spring forward)
            try:
                # Try to localize the time to check if it exists
                if bar_time.tzinfo is None:
                    if hasattr(self, "timezone") and self.timezone is not None:
                        validated = self.timezone.localize(bar_time)
                    else:
                        validated = bar_time
                else:
                    validated = bar_time

                return validated

            except pytz.NonExistentTimeError:
                # Spring forward - this time doesn't exist
                self.dst_logger.warning(
                    f"Skipping bar for non-existent time during DST spring forward: {bar_time}"
                )
                return None

            except pytz.AmbiguousTimeError:
                # Fall back - time exists twice, use DST=False (standard time)
                if hasattr(self, "timezone") and self.timezone is not None:
                    validated = self.timezone.localize(bar_time, is_dst=False)
                else:
                    validated = bar_time
                self.dst_logger.info(
                    f"Using standard time for ambiguous DST fall back time: {bar_time}"
                )
                return validated

        except Exception as e:
            self.dst_logger.error(f"Error validating DST bar time {bar_time}: {e}")
            return bar_time  # Return original on error

    def log_dst_event(
        self, event_type: str, timestamp: datetime, details: str | None = None
    ) -> None:
        """
        Log DST-related events for monitoring and debugging.

        Args:
            event_type: Type of DST event (e.g., "SPRING_FORWARD", "FALL_BACK", "TRANSITION_DETECTED")
            timestamp: Timestamp associated with the event
            details: Optional additional details
        """
        log_message = f"DST {event_type}: {timestamp}"
        if details:
            log_message += f" - {details}"

        # Include timezone information
        if hasattr(self, "timezone") and self.timezone:
            log_message += f" (timezone: {self.timezone})"

        self.dst_logger.log(self._dst_log_level, log_message)

    def get_dst_status(self) -> dict[str, Any]:
        """
        Get current DST status and information.

        Returns:
            dict: DST status information including transitions and current state
        """
        current_time = datetime.now()
        if hasattr(self, "timezone") and self.timezone:
            current_time = current_time.astimezone(self.timezone)

        status = {
            "timezone": str(getattr(self, "timezone", "UTC")),
            "current_time": current_time,
            "in_dst_transition": self.is_dst_transition_period(current_time),
            "next_dst_check": self._last_dst_check,
            "cache_size": len(self._dst_cache),
        }

        # Add current DST status
        if hasattr(self, "timezone") and self.timezone:
            try:
                localized_time = self.timezone.localize(
                    current_time.replace(tzinfo=None)
                )
                status["is_dst"] = localized_time.dst() != timedelta(0)
                status["utc_offset"] = localized_time.utcoffset()
            except Exception:
                status["is_dst"] = None
                status["utc_offset"] = None

        return status

    def clear_dst_cache(self) -> None:
        """Clear DST transition cache (useful for testing or timezone changes)."""
        self._dst_cache.clear()
        self._dst_cache_expiry.clear()
        self.dst_logger.info("DST cache cleared")

    def predict_next_dst_transition(self) -> tuple[datetime, str] | None:
        """
        Predict the next DST transition for monitoring purposes.

        Returns:
            tuple: (transition_datetime, transition_type) or None if no transitions
        """
        if not hasattr(self, "timezone") or self.timezone is None:
            return None

        tz = self.timezone
        try:
            current_time = datetime.now()
            current_year = current_time.year

            # Check transitions for current and next year
            for year in [current_year, current_year + 1]:
                transitions = self._get_dst_transitions(year)

                for transition_start, transition_end in transitions:
                    if transition_start > current_time:
                        # Determine transition type by checking offset change
                        before = transition_start - timedelta(hours=1)
                        after = transition_end + timedelta(hours=1)

                        try:
                            before_offset = tz.localize(before).utcoffset()
                            after_offset = tz.localize(after).utcoffset()

                            if before_offset is not None and after_offset is not None:
                                transition_type = (
                                    "SPRING_FORWARD"
                                    if after_offset > before_offset
                                    else "FALL_BACK"
                                )
                            else:
                                transition_type = "UNKNOWN"
                            return (transition_start, transition_type)

                        except Exception:
                            continue

        except Exception as e:
            self.dst_logger.error(f"Error predicting next DST transition: {e}")

        return None
