"""
Secure OAuth State Management Service.

Provides Redis-backed, time-limited storage for OAuth state parameters.
Replaces insecure in-memory storage that doesn't scale and loses state on restart.

SECURITY FEATURES:
- Redis-backed persistent storage (survives restarts, works with multiple workers)
- Time-limited state (expires after 10 minutes)
- Cryptographically secure state tokens
- State can only be used once (deleted after retrieval)
"""

import json
import logging
import secrets
from datetime import UTC, datetime
from typing import Any

from src.config.redis import get_redis

logger = logging.getLogger(__name__)

# OAuth state expires after 10 minutes
OAUTH_STATE_TTL_SECONDS = 600

# Redis key prefix for OAuth states
OAUTH_STATE_KEY_PREFIX = "oauth:state:"


class OAuthStateService:
    """
    Secure OAuth state management using Redis.

    This service provides secure, distributed storage for OAuth state parameters
    that works across multiple application instances and survives server restarts.
    """

    @staticmethod
    def generate_state() -> str:
        """
        Generate a cryptographically secure state token.

        Returns:
            32-byte URL-safe random string
        """
        return secrets.token_urlsafe(32)

    @staticmethod
    def store_state(state: str, data: dict[str, Any]) -> bool:
        """
        Store OAuth state data in Redis with expiration.

        Args:
            state: The state token (generated by generate_state())
            data: Dictionary of state data to store

        Returns:
            True if stored successfully, False otherwise

        SECURITY: State automatically expires after OAUTH_STATE_TTL_SECONDS
        """
        try:
            redis_client = get_redis()
            key = f"{OAUTH_STATE_KEY_PREFIX}{state}"

            # Add timestamp for audit
            data["_created_at"] = datetime.now(UTC).isoformat()

            # Store with expiration
            redis_client.setex(key, OAUTH_STATE_TTL_SECONDS, json.dumps(data))

            logger.debug(f"OAuth state stored: {state[:8]}...")
            return True

        except Exception as e:
            logger.error(f"Failed to store OAuth state: {e}")
            return False

    @staticmethod
    def retrieve_state(state: str, delete: bool = True) -> dict[str, Any] | None:
        """
        Retrieve and optionally delete OAuth state data from Redis.

        Args:
            state: The state token to retrieve
            delete: If True, delete the state after retrieval (default: True)
                   This prevents replay attacks.

        Returns:
            The stored state data, or None if not found/expired

        SECURITY:
        - By default, state is deleted after retrieval (single-use)
        - Returns None for expired or non-existent states
        """
        try:
            redis_client = get_redis()
            key = f"{OAUTH_STATE_KEY_PREFIX}{state}"

            if delete:
                # Atomic get and delete to prevent race conditions
                data = redis_client.getdel(key)
            else:
                data = redis_client.get(key)

            if data is None:
                logger.warning(f"OAuth state not found or expired: {state[:8]}...")
                return None

            result = json.loads(data)
            logger.debug(f"OAuth state retrieved: {state[:8]}...")
            return result

        except Exception as e:
            logger.error(f"Failed to retrieve OAuth state: {e}")
            return None

    @staticmethod
    def validate_state(state: str) -> bool:
        """
        Check if a state token exists without consuming it.

        Args:
            state: The state token to validate

        Returns:
            True if state exists and is valid, False otherwise
        """
        try:
            redis_client = get_redis()
            key = f"{OAUTH_STATE_KEY_PREFIX}{state}"
            return redis_client.exists(key) > 0
        except Exception as e:
            logger.warning(f"Failed to validate OAuth state: {e}")
            return False

    @staticmethod
    def delete_state(state: str) -> bool:
        """
        Explicitly delete a state token.

        Args:
            state: The state token to delete

        Returns:
            True if deleted, False if not found or error
        """
        try:
            redis_client = get_redis()
            key = f"{OAUTH_STATE_KEY_PREFIX}{state}"
            deleted = redis_client.delete(key)
            return deleted > 0
        except Exception as e:
            logger.error(f"Failed to delete OAuth state: {e}")
            return False


# Convenience functions for direct use
def create_oauth_state(data: dict[str, Any]) -> str | None:
    """
    Create a new OAuth state with the given data.

    Args:
        data: State data to store

    Returns:
        The generated state token, or None if storage failed
    """
    state = OAuthStateService.generate_state()
    if OAuthStateService.store_state(state, data):
        return state
    return None


def get_oauth_state(state: str, delete: bool = True) -> dict[str, Any] | None:
    """
    Retrieve OAuth state data.

    Args:
        state: The state token
        delete: Delete after retrieval (default: True)

    Returns:
        The state data or None
    """
    return OAuthStateService.retrieve_state(state, delete=delete)


def update_oauth_state(state: str, additional_data: dict[str, Any]) -> bool:
    """
    Update an existing OAuth state with additional data.

    This is useful for PKCE flows where the code_verifier needs to be
    stored after the initial state is created.

    Args:
        state: The state token to update
        additional_data: Additional data to merge into the existing state

    Returns:
        True if updated successfully, False otherwise
    """
    try:
        # Retrieve without deleting
        existing_data = OAuthStateService.retrieve_state(state, delete=False)
        if existing_data is None:
            logger.warning(f"Cannot update non-existent OAuth state: {state[:8]}...")
            return False

        # Merge additional data
        existing_data.update(additional_data)

        # Store updated state (this refreshes the TTL)
        return OAuthStateService.store_state(state, existing_data)

    except Exception as e:
        logger.error(f"Failed to update OAuth state: {e}")
        return False
