# 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
#

"""
Authentication utilities for CloudZero Operations CLI.

This module provides common utility functions for authentication operations,
token validation, and user information handling.
"""

import logging
from datetime import UTC, datetime, timedelta
from typing import Any

logger = logging.getLogger(__name__)


class TokenValidationError(Exception):
    """Exception raised when token validation fails."""

    pass


def is_token_valid(token: dict[str, Any] | None, buffer_minutes: int = 5) -> bool:
    """
    Check if a token is valid and not expired.

    Args:
        token: Token dictionary to validate
        buffer_minutes: Minutes of buffer before actual expiry to consider invalid

    Returns:
        True if token is valid and not expired, False otherwise
    """
    if not token or not isinstance(token, dict):
        return False

    # Check if token has expiration information
    expires_at_str = token.get("expires_at")
    if not expires_at_str:
        logger.debug("Token missing expires_at field")
        return False

    try:
        # Parse expiration time
        expires_at = datetime.fromisoformat(expires_at_str.replace("Z", "+00:00"))

        # Check if token expires within buffer period
        buffer_time = datetime.now(UTC) + timedelta(minutes=buffer_minutes)
        is_valid = expires_at > buffer_time

        if not is_valid:
            logger.debug(f"Token expired or expires within {buffer_minutes} minutes")

        return is_valid

    except (ValueError, TypeError) as e:
        logger.debug(f"Invalid expires_at format: {e}")
        return False


def is_userinfo_valid(userinfo: dict[str, Any] | None, buffer_minutes: int = 5) -> bool:
    """
    Check if cached userinfo is still valid based on associated token expiry.

    Args:
        userinfo: Userinfo dictionary to validate
        buffer_minutes: Minutes of buffer before token expiry

    Returns:
        True if userinfo is valid, False otherwise
    """
    if not userinfo or not isinstance(userinfo, dict):
        return False

    # Check if userinfo has token expiration reference
    token_expires_at_str = userinfo.get("token_expires_at")
    if not token_expires_at_str:
        logger.debug("Userinfo missing token_expires_at field")
        return False

    try:
        token_expires_at = datetime.fromisoformat(token_expires_at_str.replace("Z", "+00:00"))
        buffer_time = datetime.now(UTC) + timedelta(minutes=buffer_minutes)

        is_valid = token_expires_at > buffer_time

        if not is_valid:
            logger.debug(f"Userinfo invalid - associated token expires within {buffer_minutes} minutes")

        return is_valid

    except (ValueError, TypeError) as e:
        logger.debug(f"Invalid token_expires_at format in userinfo: {e}")
        return False


def add_token_metadata(token: dict[str, Any]) -> dict[str, Any]:
    """
    Add metadata to a token including obtained_at and expires_at timestamps.

    Args:
        token: Token dictionary from OAuth response

    Returns:
        Token dictionary with added metadata

    Raises:
        TokenValidationError: If token is missing required fields
    """
    if not token or not isinstance(token, dict):
        raise TokenValidationError("Token must be a non-empty dictionary")

    # Make a copy to avoid modifying the original
    enhanced_token = token.copy()

    # Add obtained timestamp
    enhanced_token["obtained_at"] = datetime.now(UTC).isoformat()

    # Calculate and add expiration timestamp
    expires_in = token.get("expires_in", 3600)  # Default to 1 hour

    if not isinstance(expires_in, int | float) or expires_in <= 0:
        logger.warning(f"Invalid expires_in value: {expires_in}, using default 3600")
        expires_in = 3600

    expires_at = datetime.now(UTC) + timedelta(seconds=expires_in)
    enhanced_token["expires_at"] = expires_at.isoformat()

    return enhanced_token


def add_userinfo_metadata(userinfo: dict[str, Any], token_expires_at: str) -> dict[str, Any]:
    """
    Add metadata to userinfo including cache timestamp and token expiration reference.

    Args:
        userinfo: User information dictionary
        token_expires_at: ISO format timestamp when the associated token expires

    Returns:
        Userinfo dictionary with added metadata

    Raises:
        TokenValidationError: If inputs are invalid
    """
    if not userinfo or not isinstance(userinfo, dict):
        raise TokenValidationError("Userinfo must be a non-empty dictionary")

    if not token_expires_at or not isinstance(token_expires_at, str):
        raise TokenValidationError("token_expires_at must be a non-empty string")

    # Make a copy to avoid modifying the original
    enhanced_userinfo = userinfo.copy()

    # Add cache metadata
    enhanced_userinfo["cached_at"] = datetime.now(UTC).isoformat()
    enhanced_userinfo["token_expires_at"] = token_expires_at

    return enhanced_userinfo


def extract_access_token(token: dict[str, Any] | None) -> str | None:
    """
    Safely extract access token from token dictionary.

    Args:
        token: Token dictionary

    Returns:
        Access token string or None if not found
    """
    if not token or not isinstance(token, dict):
        return None

    access_token = token.get("access_token")
    return access_token if isinstance(access_token, str) else None


def extract_refresh_token(token: dict[str, Any] | None) -> str | None:
    """
    Safely extract refresh token from token dictionary.

    Args:
        token: Token dictionary

    Returns:
        Refresh token string or None if not found
    """
    if not token or not isinstance(token, dict):
        return None

    refresh_token = token.get("refresh_token")
    return refresh_token if isinstance(refresh_token, str) else None


def calculate_time_until_expiry(expires_at_str: str) -> timedelta | None:
    """
    Calculate time remaining until token expiry.

    Args:
        expires_at_str: ISO format expiration timestamp

    Returns:
        timedelta until expiry or None if invalid
    """
    if not expires_at_str:
        return None

    try:
        expires_at = datetime.fromisoformat(expires_at_str.replace("Z", "+00:00"))
        time_left = expires_at - datetime.now(UTC)
        return time_left if time_left.total_seconds() > 0 else timedelta(0)
    except (ValueError, TypeError):
        return None


def format_time_remaining(time_delta: timedelta) -> str:
    """
    Format timedelta as human-readable string.

    Args:
        time_delta: Time remaining

    Returns:
        Formatted string like "2h 15m" or "Expired"
    """
    if time_delta.total_seconds() <= 0:
        return "Expired"

    total_seconds = int(time_delta.total_seconds())
    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60

    if hours > 0:
        return f"{hours}h {minutes}m"
    elif minutes > 0:
        return f"{minutes}m"
    else:
        return "< 1m"


def get_user_display_name(userinfo: dict[str, Any] | None) -> str:
    """
    Extract a user's display name from userinfo in order of preference.

    Args:
        userinfo: User information dictionary

    Returns:
        Display name string, defaults to "Unknown User"
    """
    if not userinfo or not isinstance(userinfo, dict):
        return "Unknown User"

    # Try different name fields in order of preference
    name_fields = ["name", "preferred_username", "email"]

    for field in name_fields:
        value = userinfo.get(field)
        if value and isinstance(value, str) and value.strip():
            return value.strip()

    # Try combining given_name and family_name
    given_name = userinfo.get("given_name", "").strip()
    family_name = userinfo.get("family_name", "").strip()

    if given_name and family_name:
        return f"{given_name} {family_name}"
    elif given_name:
        return given_name
    elif family_name:
        return family_name

    return "Unknown User"


def sanitize_token_for_logging(token: dict[str, Any] | None) -> dict[str, Any]:
    """
    Create a sanitized version of token for safe logging.

    Args:
        token: Token dictionary to sanitize

    Returns:
        Sanitized token dictionary with sensitive fields masked
    """
    if not token or not isinstance(token, dict):
        return {}

    # Sensitive fields to mask
    sensitive_fields = {"access_token", "refresh_token", "id_token"}

    sanitized = {}
    for key, value in token.items():
        if key in sensitive_fields and isinstance(value, str):
            # Show first 4 and last 4 characters
            if len(value) > 8:
                sanitized[key] = f"{value[:4]}...{value[-4:]}"
            else:
                sanitized[key] = "***"
        else:
            sanitized[key] = value

    return sanitized
