# Copyright (c) CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com
#

"""
Time and date handling utilities.

This module provides a consistent set of functions for:
- Converting between various datetime formats
- Handling UTC conversion
- Parsing dates and times from different inputs
- Formatting datetimes for display and storage
"""

from datetime import UTC, datetime
from decimal import ROUND_DOWN, Decimal
from typing import Any, NamedTuple
from zoneinfo import ZoneInfo

import dateutil.parser as parser

# Constants for timestamp conversion
MAX_32BIT_INT = 2147483647  # Used to detect millisecond timestamps

# Cutoff date for pretty date formatting
DATE_CUTOFF = datetime(year=2016, month=8, day=8, second=0).astimezone(tz=UTC)


class TimeRange(NamedTuple):
    """
    Represents a time range with start and end timestamps.
    """

    start: datetime
    end: datetime

    def __str__(self) -> str:
        """String representation of the time range."""
        return f"{self.start}-{self.end}"


def isodatetimenow() -> str:
    """
    Get the current UTC time as an ISO 8601 formatted string.

    Returns:
        ISO 8601 string representing current UTC time
    """
    return datetime.now(tz=UTC).isoformat()


def utcdatetimenow() -> datetime:
    """
    Get the current UTC time as a timezone-aware datetime object.

    Returns:
        datetime object in UTC timezone
    """
    return datetime.now(tz=UTC)


def isodatetime2datetimeutc(datetime_string: str) -> datetime:
    """
    Convert an ISO 8601 datetime string to a timezone-aware UTC datetime object.

    Args:
        datetime_string: ISO 8601 formatted datetime string

    Returns:
        timezone-aware datetime object in UTC

    Examples:
        >>> isodatetime2datetimeutc('2020-02-04T22:04:27.799278')
        datetime.datetime(2020, 2, 4, 22, 4, 27, 799278, tzinfo=datetime.timezone.utc)

        >>> isodatetime2datetimeutc('2020-02-04T22:04:27.799278+01:00')
        datetime.datetime(2020, 2, 4, 21, 4, 27, 799278, tzinfo=datetime.timezone.utc)
    """
    parsed_datetime = parser.parse(datetime_string)
    if parsed_datetime.tzinfo is None:
        return parsed_datetime.replace(tzinfo=UTC)
    else:
        return parsed_datetime.astimezone(tz=UTC)


def utc_datetime_from_anything(input_data: Any) -> datetime | None:
    """
    Convert various input types to a timezone-aware UTC datetime object.

    Args:
        input_data: Input value to convert (None, datetime, string, timestamp)

    Returns:
        timezone-aware datetime object in UTC, or None if input is None

    Raises:
        ValueError: If the input cannot be parsed into a datetime
    """
    if input_data is None:
        return None

    try:
        if isinstance(input_data, datetime):
            parsed_datetime = input_data
        elif isinstance(input_data, str):
            parsed_datetime = parser.parse(input_data)
        else:
            # if it's larger than the max size for a 32 bit integer it's in ms
            if input_data > MAX_32BIT_INT:
                input_data /= 1000
            parsed_datetime = datetime.fromtimestamp(input_data, tz=UTC)

        if parsed_datetime.tzinfo is None:
            return parsed_datetime.replace(tzinfo=UTC)
        else:
            return parsed_datetime.astimezone(tz=UTC)
    except (parser.ParserError, TypeError, ValueError) as e:
        # Raise ValueError with informative message rather than return a default value
        raise ValueError(f"Could not parse '{input_data}' into a datetime: {e}") from e


def utc_iso_from_anything(input_data: Any) -> str | None:
    """
    Convert various input types to an ISO 8601 formatted UTC string.

    Args:
        input_data: Input value to convert (None, datetime, string, timestamp)

    Returns:
        ISO 8601 formatted string in UTC timezone, or None if input is None
        If the input is a string that can't be parsed, returns the original string

    Examples:
        >>> utc_iso_from_anything(None)

        >>> utc_iso_from_anything(datetime(2020, 1, 1, 12, tzinfo=UTC))
        '2020-01-01T12:00:00+00:00'

        >>> utc_iso_from_anything("2019-12-23T00:00:00+00:00")
        '2019-12-23T00:00:00+00:00'

        >>> utc_iso_from_anything("not-a-date")
        'not-a-date'
    """
    if input_data is None:
        return None

    # Special case for unparseable strings - just return them as is
    if isinstance(input_data, str):
        try:
            # Try to parse first - this is to validate it's a real date
            parser.parse(input_data)
        except (parser.ParserError, ValueError, TypeError):
            # If it's not parseable, return the original string
            return input_data

    # For valid dates or non-string inputs, use the normal path
    try:
        parsed_datetime = utc_datetime_from_anything(input_data)
        return parsed_datetime.isoformat() if parsed_datetime else None
    except ValueError:
        # If parsing fails and input is a string, return the original string
        if isinstance(input_data, str):
            return input_data
        # For other types of input, return None
        return None


def isofromtimestamp(timestamp: float) -> str:
    """
    Convert a Unix timestamp to an ISO 8601 formatted UTC string.

    Args:
        timestamp: Unix timestamp in seconds

    Returns:
        ISO 8601 formatted string in UTC timezone

    Examples:
        >>> isofromtimestamp(1571265346.021007)
        '2019-10-16T22:35:46.021007+00:00'
    """
    return datetime.fromtimestamp(timestamp, tz=UTC).isoformat()


def utctimestampnow() -> Decimal:
    """
    Get current UTC timestamp as a Decimal with 5 decimal places.

    Returns:
        Current UTC timestamp as Decimal with 5 decimal place precision
    """
    return Decimal(str(datetime.now(tz=UTC).timestamp())).quantize(Decimal(".00000"), rounding=ROUND_DOWN)


def pretty_date(any_date: Any, _local: bool = False) -> str:
    """
    Format a date in a user-friendly format (YYYY-Mon-DD).

    Only returns a formatted date if it's newer than the cutoff date.

    Args:
        any_date: Any date/time input (parsed with utc_datetime_from_anything)
        local: Whether to use local timezone (not currently implemented)

    Returns:
        Formatted date string, or empty string for old, unparseable, or None dates
    """
    if any_date is None:
        return ""

    try:
        datetime_input = utc_datetime_from_anything(any_date)
        if datetime_input and datetime_input > DATE_CUTOFF:
            return datetime_input.strftime("%Y-%b-%d")
        else:
            return ""
    except ValueError:
        # Return empty string for unparseable dates
        if isinstance(any_date, str):
            return any_date
        return ""


def pretty_datetime_utc(any_date: Any) -> str:
    """
    Format a datetime in a user-friendly format with UTC timezone.

    Args:
        any_date: Any date/time input (parsed with utc_datetime_from_anything)

    Returns:
        Formatted datetime string in UTC timezone, or empty string for None or unparseable input
        If input is an unparseable string, returns the original string

    Examples:
        >>> pretty_datetime_utc('2019-10-16T22:35:46.021007+00:00')
        '2019-Oct-16 22:35:46 UTC'
    """
    if not any_date:
        return ""

    try:
        datetime_input = utc_datetime_from_anything(any_date)
        return datetime_input.strftime("%Y-%b-%d %H:%M:%S %Z")
    except ValueError:
        # Return original string for unparseable string inputs
        if isinstance(any_date, str):
            return any_date
        return ""


def pretty_datetime_local(any_date: Any, local_timezone: ZoneInfo | None = None) -> str:
    """
    Format a datetime in a user-friendly format with local timezone.

    Args:
        any_date: Any date/time input (parsed with utc_datetime_from_anything)
        local_timezone: Optional specific timezone to convert to. If None, uses system local timezone.

    Returns:
        Formatted datetime string in local timezone, or empty string for None input
        If input is an unparseable string, returns the original string
    """
    if not any_date:
        return ""

    try:
        datetime_input = utc_datetime_from_anything(any_date)

        if local_timezone:
            return datetime_input.astimezone(local_timezone).strftime("%Y-%b-%d %H:%M:%S %Z")
        else:
            return datetime_input.astimezone().strftime("%Y-%b-%d %H:%M:%S %Z")
    except ValueError:
        # Return original string for unparseable string inputs
        if isinstance(any_date, str):
            return any_date
        return ""


def datetime_to_utc_database_timeformat(datetime_input: Any) -> str:
    """
    Convert datetime object to a format for database import.

    Formats the datetime in a way that RedShift/Athena/Aurora can import automatically.
    See: https://docs.aws.amazon.com/redshift/latest/dg/automatic-recognition.html

    Args:
        datetime_input: A python datetime object

    Returns:
        String in format YYYY-MM-DD HH:MI:SS in UTC timezone
        If input is an unparseable string, returns the original string

    Examples:
        >>> datetime_to_utc_database_timeformat(datetime(2009, 4, 19, 21, 12, 42, tzinfo=UTC))
        '2009-04-19 21:12:42'
    """
    try:
        dt = utc_datetime_from_anything(datetime_input)
        return dt.strftime("%Y-%m-%d %H:%M:%S")
    except ValueError:
        # Return original string for unparseable string inputs
        if isinstance(datetime_input, str):
            return datetime_input
        return ""
