"""Datetime accessor for datetime operations."""
from __future__ import annotations

import calendar
from typing import TYPE_CHECKING
from zoneinfo import ZoneInfo

from cqla.exprs import base as exprs_base
from cqla.accessors.base import Accessor

if TYPE_CHECKING:
    from cqla.exprs.base import Expr


class Datetime(Accessor):
    """Accessor for datetime operations."""

    def _expr_repr(self) -> str:
        return f"{self._expr!r}.dt"

    # --- extraction (numeric) ---

    def year(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).year,
            f"{self._expr_repr()}.year()"
        )

    def month(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).month,
            f"{self._expr_repr()}.month()"
        )

    def day(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).day,
            f"{self._expr_repr()}.day()"
        )

    def hour(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).hour,
            f"{self._expr_repr()}.hour()"
        )

    def minute(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).minute,
            f"{self._expr_repr()}.minute()"
        )

    def second(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).second,
            f"{self._expr_repr()}.second()"
        )

    def millisecond(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).microsecond // 1000,
            f"{self._expr_repr()}.millisecond()"
        )

    def microsecond(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).microsecond,
            f"{self._expr_repr()}.microsecond()"
        )

    def nanosecond(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).microsecond * 1000,
            f"{self._expr_repr()}.nanosecond()"
        )

    def weekday(self) -> Expr:
        """ISO weekday: Monday=1, Sunday=7."""
        return exprs_base.Expr(
            lambda r: self._expr(r).isoweekday(),
            f"{self._expr_repr()}.weekday()"
        )

    def week(self) -> Expr:
        """ISO week number."""
        return exprs_base.Expr(
            lambda r: self._expr(r).isocalendar()[1],
            f"{self._expr_repr()}.week()"
        )

    def ordinal_day(self) -> Expr:
        """Day of year (1-366)."""
        return exprs_base.Expr(
            lambda r: self._expr(r).timetuple().tm_yday,
            f"{self._expr_repr()}.ordinal_day()"
        )

    def quarter(self) -> Expr:
        return exprs_base.Expr(
            lambda r: (self._expr(r).month - 1) // 3 + 1,
            f"{self._expr_repr()}.quarter()"
        )

    def iso_year(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).isocalendar()[0],
            f"{self._expr_repr()}.iso_year()"
        )

    def century(self) -> Expr:
        return exprs_base.Expr(
            lambda r: (self._expr(r).year - 1) // 100 + 1,
            f"{self._expr_repr()}.century()"
        )

    def millennium(self) -> Expr:
        return exprs_base.Expr(
            lambda r: (self._expr(r).year - 1) // 1000 + 1,
            f"{self._expr_repr()}.millennium()"
        )

    # --- boolean ---

    def is_leap_year(self) -> Expr:
        return exprs_base.Expr(
            lambda r: calendar.isleap(self._expr(r).year),
            f"{self._expr_repr()}.is_leap_year()"
        )

    # --- date/time extraction ---

    def date(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).date(),
            f"{self._expr_repr()}.date()"
        )

    def time(self) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).time(),
            f"{self._expr_repr()}.time()"
        )

    # --- formatting ---

    def strftime(self, fmt: str) -> Expr:
        return exprs_base.Expr(
            lambda r: self._expr(r).strftime(fmt),
            f"{self._expr_repr()}.strftime({fmt!r})"
        )

    def to_string(self, fmt: str) -> Expr:
        """Alias for strftime."""
        return self.strftime(fmt)

    # --- epoch / timestamp ---

    def epoch(self, time_unit: str = 'us') -> Expr:
        multipliers = {'s': 1, 'ms': 1_000, 'us': 1_000_000, 'ns': 1_000_000_000}
        mul = multipliers[time_unit]
        return exprs_base.Expr(
            lambda r: int(self._expr(r).timestamp() * mul),
            f"{self._expr_repr()}.epoch({time_unit!r})"
        )

    def timestamp(self, time_unit: str = 'us') -> Expr:
        """Alias for epoch."""
        return self.epoch(time_unit)

    # --- timezone ---

    def convert_time_zone(self, tz: str) -> Expr:
        zone = ZoneInfo(tz)
        return exprs_base.Expr(
            lambda r: self._expr(r).astimezone(zone),
            f"{self._expr_repr()}.convert_time_zone({tz!r})"
        )

    def replace_time_zone(self, tz: str | None) -> Expr:
        zone = ZoneInfo(tz) if tz is not None else None
        return exprs_base.Expr(
            lambda r: self._expr(r).replace(tzinfo=zone),
            f"{self._expr_repr()}.replace_time_zone({tz!r})"
        )

    # --- manipulation ---

    def replace(self, **kwargs) -> Expr:
        parts_repr = ', '.join(f'{k}={v!r}' for k, v in kwargs.items())
        return exprs_base.Expr(
            lambda r: self._expr(r).replace(**kwargs),
            f"{self._expr_repr()}.replace({parts_repr})"
        )
