"""API Key service."""

import uuid
from datetime import datetime, timedelta, timezone
from typing import Optional, List

from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession

from src.models.api_key import APIKey
from src.models.user import User
from src.core.security import generate_api_key, get_key_prefix, verify_api_key


class APIKeyError(Exception):
    """Base API key error."""

    pass


class APIKeyNotFoundError(APIKeyError):
    """API key not found."""

    pass


class APIKeyRevokedError(APIKeyError):
    """API key has been revoked."""

    pass


class APIKeyExpiredError(APIKeyError):
    """API key has expired."""

    pass


async def create_api_key(
    session: AsyncSession,
    user_id: uuid.UUID,
    name: Optional[str] = None,
    scopes: Optional[List[str]] = None,
    expires_days: Optional[int] = None,
) -> tuple[APIKey, str]:
    """Create a new API key for a user.

    Args:
        session: Database session
        user_id: User ID
        name: Optional name for the key
        scopes: List of permission scopes
        expires_days: Optional expiration in days

    Returns:
        Tuple of (APIKey object, full_key string)
        Note: full_key is shown ONLY ONCE at creation!
    """
    # Generate key and hash
    full_key, key_hash = generate_api_key()
    key_prefix = get_key_prefix(full_key)

    # Calculate expiration
    expires_at = None
    if expires_days:
        expires_at = datetime.now(timezone.utc) + timedelta(days=expires_days)

    # Create API key record
    api_key = APIKey(
        user_id=user_id,
        key_hash=key_hash,
        key_prefix=key_prefix,
        name=name,
        scopes=scopes or [],
        expires_at=expires_at,
        is_active=True,
        created_at=datetime.now(timezone.utc),
    )

    session.add(api_key)
    await session.commit()
    await session.refresh(api_key)

    return api_key, full_key


async def validate_api_key(
    session: AsyncSession,
    key: str,
) -> Optional[User]:
    """Validate an API key and return the associated user.

    Args:
        session: Database session
        key: Full API key

    Returns:
        User object if key is valid, None otherwise
    """
    if not key.startswith("mk_"):
        return None

    # Extract prefix for initial lookup
    key_prefix = get_key_prefix(key)

    # Find all active API keys with matching prefix
    result = await session.execute(
        select(APIKey).where(
            and_(
                APIKey.key_prefix == key_prefix,
                APIKey.is_active == True,
            )
        )
    )
    api_keys = result.scalars().all()

    # Check each key's hash
    for api_key in api_keys:
        if verify_api_key(key, api_key.key_hash):
            # Check if expired
            if api_key.expires_at and api_key.expires_at < datetime.now(timezone.utc):
                return None

            # Update last used
            api_key.last_used_at = datetime.now(timezone.utc)
            await session.commit()

            # Return user
            result = await session.execute(
                select(User).where(User.id == api_key.user_id)
            )
            user = result.scalar_one_or_none()

            if user and user.is_active:
                return user
            return None

    return None


async def list_api_keys(
    session: AsyncSession,
    user_id: uuid.UUID,
) -> List[APIKey]:
    """List all API keys for a user (without key_hash).

    Args:
        session: Database session
        user_id: User ID

    Returns:
        List of APIKey objects
    """
    result = await session.execute(
        select(APIKey)
        .where(APIKey.user_id == user_id)
        .order_by(APIKey.created_at.desc())
    )
    return list(result.scalars().all())


async def get_api_key(
    session: AsyncSession,
    api_key_id: uuid.UUID,
    user_id: Optional[uuid.UUID] = None,
) -> Optional[APIKey]:
    """Get a specific API key by ID.

    Args:
        session: Database session
        api_key_id: API key ID
        user_id: Optional user ID to verify ownership

    Returns:
        APIKey object or None
    """
    query = select(APIKey).where(APIKey.id == api_key_id)

    if user_id:
        query = query.where(APIKey.user_id == user_id)

    result = await session.execute(query)
    return result.scalar_one_or_none()


async def revoke_api_key(
    session: AsyncSession,
    api_key_id: uuid.UUID,
    user_id: uuid.UUID,
) -> bool:
    """Revoke an API key.

    Args:
        session: Database session
        api_key_id: API key ID
        user_id: User ID (for ownership verification)

    Returns:
        True if revoked successfully

    Raises:
        APIKeyNotFoundError: If key not found
    """
    api_key = await get_api_key(session, api_key_id, user_id)

    if not api_key:
        raise APIKeyNotFoundError("API key not found")

    api_key.is_active = False
    await session.commit()

    return True


async def rotate_api_key(
    session: AsyncSession,
    api_key_id: uuid.UUID,
    user_id: uuid.UUID,
) -> tuple[APIKey, str]:
    """Rotate (regenerate) an API key.

    Args:
        session: Database session
        api_key_id: API key ID to rotate
        user_id: User ID (for ownership verification)

    Returns:
        Tuple of (new APIKey object, new full_key string)

    Raises:
        APIKeyNotFoundError: If key not found
    """
    # Get existing key
    old_key = await get_api_key(session, api_key_id, user_id)

    if not old_key:
        raise APIKeyNotFoundError("API key not found")

    # Revoke old key
    old_key.is_active = False

    # Generate new key
    full_key, key_hash = generate_api_key()
    key_prefix = get_key_prefix(full_key)

    # Create new API key with same settings
    new_key = APIKey(
        user_id=user_id,
        key_hash=key_hash,
        key_prefix=key_prefix,
        name=old_key.name,
        scopes=old_key.scopes,
        expires_at=old_key.expires_at,
        is_active=True,
        created_at=datetime.now(timezone.utc),
    )

    session.add(new_key)
    await session.commit()
    await session.refresh(new_key)

    return new_key, full_key


async def update_api_key(
    session: AsyncSession,
    api_key_id: uuid.UUID,
    user_id: uuid.UUID,
    name: Optional[str] = None,
) -> APIKey:
    """Update API key metadata.

    Args:
        session: Database session
        api_key_id: API key ID
        user_id: User ID (for ownership verification)
        name: New name for the key

    Returns:
        Updated APIKey object

    Raises:
        APIKeyNotFoundError: If key not found
    """
    api_key = await get_api_key(session, api_key_id, user_id)

    if not api_key:
        raise APIKeyNotFoundError("API key not found")

    if name is not None:
        api_key.name = name

    await session.commit()
    await session.refresh(api_key)

    return api_key
