"""Lightweight MCP server for date, time, and day of week."""

from calendar import monthrange
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

from fastmcp import FastMCP

mcp = FastMCP("mcp-datetimeday")


@mcp.tool()
def get_datetime(
    tz: str | None = None,
    format: str | None = None,
) -> dict:
    """
    Get current date and time with day of week.

    Args:
        tz: IANA timezone (e.g., "America/New_York", "UTC"). Defaults to local.
        format: Output format - "iso8601", "unix", "human", or None for full response.

    Returns:
        Formatted datetime with day of week always included.
    """
    if tz:
        try:
            zone = ZoneInfo(tz) if tz != "UTC" else timezone.utc
            now = datetime.now(zone)
        except Exception:
            return {"error": f"Invalid timezone: {tz}"}
    else:
        now = datetime.now().astimezone()

    day_of_week = now.strftime("%A")

    if format == "iso8601":
        return {"day_of_week": day_of_week, "iso8601": now.isoformat()}
    elif format == "unix":
        return {"day_of_week": day_of_week, "unix_timestamp": int(now.timestamp())}
    elif format == "human":
        return {
            "day_of_week": day_of_week,
            "human_readable": now.strftime("%A, %B %d, %Y at %I:%M %p"),
        }

    # Default: full response
    return {
        "day_of_week": day_of_week,
        "date": now.strftime("%Y-%m-%d"),
        "time": now.strftime("%H:%M:%S"),
        "timezone": str(now.tzinfo),
        "utc_offset": now.strftime("%z"),
        "iso8601": now.isoformat(),
        "unix_timestamp": int(now.timestamp()),
        "human_readable": now.strftime("%A, %B %d, %Y at %I:%M %p"),
    }


@mcp.tool()
def relative_time(date_str: str, reference: str | None = None) -> dict:
    """
    Get relative time description between two dates.

    Args:
        date_str: Target date in YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS format.
        reference: Reference date (same formats). Defaults to now.

    Returns:
        Relative time description (e.g., "3 days ago", "in 2 weeks").
    """
    formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"]

    target = None
    for fmt in formats:
        try:
            target = datetime.strptime(date_str, fmt)
            break
        except ValueError:
            continue

    if not target:
        return {"error": f"Invalid date format: {date_str}. Use YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS."}

    if reference:
        ref = None
        for fmt in formats:
            try:
                ref = datetime.strptime(reference, fmt)
                break
            except ValueError:
                continue
        if not ref:
            return {"error": f"Invalid reference date format: {reference}"}
    else:
        ref = datetime.now()

    delta = target - ref
    total_seconds = delta.total_seconds()
    is_future = total_seconds > 0
    abs_seconds = abs(total_seconds)

    # Calculate components
    days = int(abs_seconds // 86400)
    hours = int((abs_seconds % 86400) // 3600)
    minutes = int((abs_seconds % 3600) // 60)
    seconds = int(abs_seconds % 60)

    # Build description
    if days >= 365:
        years = days // 365
        unit = "year" if years == 1 else "years"
        desc = f"{years} {unit}"
    elif days >= 30:
        months = days // 30
        unit = "month" if months == 1 else "months"
        desc = f"{months} {unit}"
    elif days >= 7:
        weeks = days // 7
        unit = "week" if weeks == 1 else "weeks"
        desc = f"{weeks} {unit}"
    elif days > 0:
        unit = "day" if days == 1 else "days"
        desc = f"{days} {unit}"
    elif hours > 0:
        unit = "hour" if hours == 1 else "hours"
        desc = f"{hours} {unit}"
    elif minutes > 0:
        unit = "minute" if minutes == 1 else "minutes"
        desc = f"{minutes} {unit}"
    else:
        unit = "second" if seconds == 1 else "seconds"
        desc = f"{seconds} {unit}"

    relative = f"in {desc}" if is_future else f"{desc} ago"
    if abs_seconds < 1:
        relative = "now"

    return {
        "target": date_str,
        "target_day_of_week": target.strftime("%A"),
        "reference": reference or "now",
        "relative": relative,
        "days_difference": delta.days,
        "total_seconds": int(total_seconds),
    }


@mcp.tool()
def days_in_month(year: int | None = None, month: int | None = None) -> dict:
    """
    Get the number of days in a month.

    Args:
        year: Year (e.g., 2025). Defaults to current year.
        month: Month (1-12). Defaults to current month.

    Returns:
        Number of days in the month, plus first/last day info.
    """
    now = datetime.now()
    year = year or now.year
    month = month or now.month

    if not 1 <= month <= 12:
        return {"error": f"Invalid month: {month}. Must be 1-12."}

    first_weekday, num_days = monthrange(year, month)
    first_day = datetime(year, month, 1)
    last_day = datetime(year, month, num_days)

    return {
        "year": year,
        "month": month,
        "month_name": first_day.strftime("%B"),
        "days_in_month": num_days,
        "first_day": {
            "date": first_day.strftime("%Y-%m-%d"),
            "day_of_week": first_day.strftime("%A"),
        },
        "last_day": {
            "date": last_day.strftime("%Y-%m-%d"),
            "day_of_week": last_day.strftime("%A"),
        },
        "is_leap_year": year % 4 == 0 and (year % 100 != 0 or year % 400 == 0),
    }


@mcp.tool()
def convert_time(
    time_str: str,
    from_tz: str,
    to_tz: str,
) -> dict:
    """
    Convert time between timezones.

    Args:
        time_str: Time in YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS format.
        from_tz: Source IANA timezone (e.g., "America/New_York", "UTC").
        to_tz: Target IANA timezone.

    Returns:
        Converted time with day of week for both timezones.
    """
    formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d"]

    dt = None
    for fmt in formats:
        try:
            dt = datetime.strptime(time_str, fmt)
            break
        except ValueError:
            continue

    if not dt:
        return {"error": f"Invalid time format: {time_str}"}

    try:
        source_zone = ZoneInfo(from_tz) if from_tz != "UTC" else timezone.utc
        target_zone = ZoneInfo(to_tz) if to_tz != "UTC" else timezone.utc
    except Exception as e:
        return {"error": f"Invalid timezone: {e}"}

    source_dt = dt.replace(tzinfo=source_zone)
    target_dt = source_dt.astimezone(target_zone)

    return {
        "from": {
            "day_of_week": source_dt.strftime("%A"),
            "datetime": source_dt.strftime("%Y-%m-%d %H:%M:%S"),
            "timezone": from_tz,
            "utc_offset": source_dt.strftime("%z"),
        },
        "to": {
            "day_of_week": target_dt.strftime("%A"),
            "datetime": target_dt.strftime("%Y-%m-%d %H:%M:%S"),
            "timezone": to_tz,
            "utc_offset": target_dt.strftime("%z"),
        },
    }


@mcp.tool()
def get_week_year(date_str: str | None = None) -> dict:
    """
    Get week number and ISO week of the year.

    Args:
        date_str: Date in YYYY-MM-DD format. Defaults to today.

    Returns:
        Week number, ISO week, day of year, and related info.
    """
    if date_str:
        try:
            dt = datetime.strptime(date_str, "%Y-%m-%d")
        except ValueError:
            return {"error": f"Invalid date format: {date_str}. Use YYYY-MM-DD."}
    else:
        dt = datetime.now()

    iso_cal = dt.isocalendar()

    return {
        "date": dt.strftime("%Y-%m-%d"),
        "day_of_week": dt.strftime("%A"),
        "day_of_week_number": dt.isoweekday(),  # 1=Monday, 7=Sunday
        "week_number": (dt.timetuple().tm_yday - 1) // 7 + 1,  # Simple week count
        "iso_week": iso_cal[1],
        "iso_year": iso_cal[0],  # ISO year (can differ from calendar year)
        "day_of_year": dt.timetuple().tm_yday,
        "days_remaining_in_year": (datetime(dt.year, 12, 31) - dt).days,
        "is_weekend": dt.isoweekday() >= 6,
        "quarter": (dt.month - 1) // 3 + 1,
    }


def main():
    """Run the MCP server."""
    mcp.run()


if __name__ == "__main__":
    main()
