"""
Module: calendar.py
Description: Provides calendar integration capabilities for the Genesis system.
              This module allows scheduling awareness and event management.
"""

import logging
import datetime
from typing import List, Dict, Optional, Union

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


class CalendarEvent:
    """
    Represents a calendar event with details like start time, end time, and description.
    """

    def __init__(self, start_time: datetime.datetime, end_time: datetime.datetime, description: str):
        """
        Initializes a CalendarEvent object.

        Args:
            start_time: The start time of the event.
            end_time: The end time of the event.
            description: A description of the event.
        """
        if not isinstance(start_time, datetime.datetime):
            raise TypeError("start_time must be a datetime.datetime object")
        if not isinstance(end_time, datetime.datetime):
            raise TypeError("end_time must be a datetime.datetime object")
        if not isinstance(description, str):
            raise TypeError("description must be a string")

        if start_time >= end_time:
            raise ValueError("start_time must be before end_time")

        self.start_time = start_time
        self.end_time = end_time
        self.description = description

    def __repr__(self):
        return f"CalendarEvent(start_time={self.start_time}, end_time={self.end_time}, description='{self.description}')"

    def to_dict(self) -> Dict[str, Union[str, float]]:
        """
        Converts the CalendarEvent object to a dictionary.

        Returns:
            A dictionary representation of the event.
        """
        return {
            "start_time": self.start_time.isoformat(),
            "end_time": self.end_time.isoformat(),
            "description": self.description
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Union[str, float]]) -> 'CalendarEvent':
        """
        Creates a CalendarEvent object from a dictionary.

        Args:
            data: A dictionary containing the event data.  Must include 'start_time', 'end_time', and 'description' keys.
                  'start_time' and 'end_time' should be ISO format strings.

        Returns:
            A CalendarEvent object.
        """
        try:
            start_time = datetime.datetime.fromisoformat(data['start_time'])
            end_time = datetime.datetime.fromisoformat(data['end_time'])
            description = str(data['description'])
            return cls(start_time, end_time, description)
        except KeyError as e:
            raise ValueError(f"Missing key in data: {e}")
        except ValueError as e:
            raise ValueError(f"Invalid data format: {e}")
        except Exception as e:
            logging.error(f"Error creating CalendarEvent from dictionary: {e}")
            raise

class CalendarIntegration:
    """
    Provides methods for managing calendar events.
    """

    def __init__(self):
        """
        Initializes the CalendarIntegration object.  In a real system, this would likely connect to a calendar service.
        """
        self.events: List[CalendarEvent] = []
        logging.info("CalendarIntegration initialized.")

    def add_event(self, event: CalendarEvent) -> None:
        """
        Adds an event to the calendar.

        Args:
            event: The CalendarEvent object to add.
        """
        if not isinstance(event, CalendarEvent):
            raise TypeError("event must be a CalendarEvent object")

        self.events.append(event)
        logging.info(f"Event added: {event}")

    def get_events_by_date(self, date: datetime.date) -> List[CalendarEvent]:
        """
        Retrieves all events for a given date.

        Args:
            date: The date for which to retrieve events.

        Returns:
            A list of CalendarEvent objects for the given date.
        """
        if not isinstance(date, datetime.date):
            raise TypeError("date must be a datetime.date object")

        events_on_date = [
            event for event in self.events
            if event.start_time.date() == date
        ]
        logging.info(f"Retrieved {len(events_on_date)} events for date: {date}")
        return events_on_date

    def get_next_event(self) -> Optional[CalendarEvent]:
        """
        Retrieves the next upcoming event.

        Returns:
            The next upcoming CalendarEvent object, or None if there are no future events.
        """
        now = datetime.datetime.now()
        future_events = [event for event in self.events if event.start_time > now]
        if not future_events:
            logging.info("No upcoming events found.")
            return None

        next_event = min(future_events, key=lambda event: event.start_time)
        logging.info(f"Next event: {next_event}")
        return next_event

    def remove_event(self, event: CalendarEvent) -> None:
        """
        Removes an event from the calendar.

        Args:
            event: The CalendarEvent object to remove.
        """
        if not isinstance(event, CalendarEvent):
            raise TypeError("event must be a CalendarEvent object")

        try:
            self.events.remove(event)
            logging.info(f"Event removed: {event}")
        except ValueError:
            logging.warning(f"Event not found: {event}")

    def clear_all_events(self) -> None:
        """Clears all events from the calendar."""

        self.events = []
        logging.info("All events cleared from the calendar.")

    def is_time_available(self, start_time: datetime.datetime, end_time: datetime.datetime) -> bool:
        """
        Checks if a given time slot is available (no overlapping events).

        Args:
            start_time: The start time of the time slot to check.
            end_time: The end time of the time slot to check.

        Returns:
            True if the time slot is available, False otherwise.
        """
        if not isinstance(start_time, datetime.datetime):
            raise TypeError("start_time must be a datetime.datetime object")
        if not isinstance(end_time, datetime.datetime):
            raise TypeError("end_time must be a datetime.datetime object")

        if start_time >= end_time:
            raise ValueError("start_time must be before end_time")

        for event in self.events:
            if (start_time < event.end_time) and (end_time > event.start_time):
                logging.info(f"Time slot not available. Overlaps with: {event}")
                return False

        logging.info("Time slot available.")
        return True

# Example Usage (for testing):
if __name__ == "__main__":
    calendar = CalendarIntegration()

    # Add some events
    event1 = CalendarEvent(
        start_time=datetime.datetime(2024, 1, 20, 10, 0, 0),
        end_time=datetime.datetime(2024, 1, 20, 11, 0, 0),
        description="Meeting with John"
    )
    event2 = CalendarEvent(
        start_time=datetime.datetime(2024, 1, 20, 14, 0, 0),
        end_time=datetime.datetime(2024, 1, 20, 15, 0, 0),
        description="Team Lunch"
    )
    calendar.add_event(event1)
    calendar.add_event(event2)

    # Get events for a specific date
    date_to_check = datetime.date(2024, 1, 20)
    events_on_date = calendar.get_events_by_date(date_to_check)
    print(f"Events on {date_to_check}: {events_on_date}")

    # Get the next event
    next_event = calendar.get_next_event()
    print(f"Next event: {next_event}")

    # Check availability
    start_time_check = datetime.datetime(2024, 1, 20, 11, 30, 0)
    end_time_check = datetime.datetime(2024, 1, 20, 13, 0, 0)
    is_available = calendar.is_time_available(start_time_check, end_time_check)
    print(f"Time slot available: {is_available}")

    # Remove an event
    calendar.remove_event(event1)
    print(f"Events on {date_to_check} after removal: {calendar.get_events_by_date(date_to_check)}")

    # Clear all events
    calendar.clear_all_events()
    print(f"Events after clearing: {calendar.events}")

    # Demonstrate from_dict and to_dict
    event_dict = event2.to_dict()
    print(f"Event as dictionary: {event_dict}")

    event_from_dict = CalendarEvent.from_dict(event_dict)
    print(f"Event from dictionary: {event_from_dict}")

    # Demonstrate error handling:
    try:
        invalid_event = CalendarEvent(
            start_time="not a datetime",  # type: ignore
            end_time=datetime.datetime(2024, 1, 21, 10, 0, 0),
            description="Invalid Event"
        )
    except TypeError as e:
        print(f"Error creating invalid event: {e}")

    try:
        invalid_event_dict = {"start_time": "invalid date", "end_time": "2024-01-22T10:00:00", "description": "bad event"}
        bad_event = CalendarEvent.from_dict(invalid_event_dict)
    except ValueError as e:
        print(f"Error creating event from invalid dict: {e}")