"""
Authentication controller for handling auth-related business logic.
"""

import uuid
from datetime import datetime, timedelta, timezone
from typing import Dict, Optional, Tuple, Union
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import select, and_, update
from pydantic.networks import EmailStr

from src.apps.users.models.user import User
from src.apps.auth.models.auth_session import AuthSession
from src.apps.auth.schemas.auth_common import (
    LoginRequestSchema, 
    LoginResponseSchema, 
    RegisterRequestSchema,
    RegisterResponseSchema,
    RefreshTokenRequestSchema,
    AuthTokenSchema,
    LogoutResponseSchema,
    UserProfileSchema
)
from src.apps.users.schemas.user_common import UserCreateSchema
from src.apps.users.services import create_user
from src.apps.auth.utils.jwt import jwt_manager
from src.core.utils.password import verify_password
from src.core.config import settings


def authenticate_user(db: Session, email: EmailStr, password: str) -> Optional[User]:
    """
    Authenticate user by email and password.
    
    Args:
        db: Database session
        email: User's email address
        password: Plain text password
        
    Returns:
        User model if authentication successful, None otherwise
    """
    # Get user by email with direct query
    stmt = select(User).where(User.email == email)
    if hasattr(User, 'deleted_at'):
        stmt = stmt.where(User.deleted_at.is_(None))
    
    user = db.execute(stmt).scalar_one_or_none()
    if not user:
        return None
    
    # Verify password
    if not verify_password(password, user.hashed_password):
        return None
    
    return user


def create_auth_session(
    db: Session,
    user_id: int,
    access_token: str,
    refresh_token: str,
    access_token_expires_at: datetime,
    refresh_token_expires_at: datetime,
    ip_address: Optional[str] = None,
    user_agent: Optional[str] = None,
    device_info: Optional[str] = None
) -> Optional[AuthSession]:
    """Create a new authentication session."""
    try:
        auth_session = AuthSession(
            user_id=user_id,
            access_token=access_token,
            refresh_token=refresh_token,
            access_token_expires_at=access_token_expires_at,
            refresh_token_expires_at=refresh_token_expires_at,
            ip_address=ip_address,
            user_agent=user_agent,
            device_info=device_info,
            is_active=True,
            is_revoked=False
        )
        
        db.add(auth_session)
        db.commit()
        db.refresh(auth_session)
        
        return auth_session
    except Exception as e:
        db.rollback()
        return None


def revoke_all_user_sessions(db: Session, user_id: int) -> int:
    """Revoke all active sessions for a user."""
    try:
        now = datetime.now(timezone.utc)
        
        # Update all active sessions to revoked
        stmt = (
            update(AuthSession)
            .where(
                and_(
                    AuthSession.user_id == user_id,
                    AuthSession.is_active == True,
                    AuthSession.is_revoked == False
                )
            )
            .values(
                is_active=False,
                is_revoked=True,
                revoked_at=now,
                updated_at=now
            )
        )
        
        result = db.execute(stmt)
        db.commit()
        
        return result.rowcount
    except Exception:
        db.rollback()
        return 0


def revoke_session_by_token(db: Session, access_token: str) -> bool:
    """
    Revoke a specific session by access token.
    
    Args:
        db: Database session
        access_token: The access token to revoke
        
    Returns:
        True if session was revoked, False otherwise
    """
    try:
        now = datetime.now(timezone.utc)
        
        # Find and revoke the session with the specific access token
        stmt = (
            update(AuthSession)
            .where(
                and_(
                    AuthSession.access_token == access_token,
                    AuthSession.is_active == True,
                    AuthSession.is_revoked == False
                )
            )
            .values(
                is_active=False,
                is_revoked=True,
                revoked_at=now,
                updated_at=now
            )
        )
        
        result = db.execute(stmt)
        db.commit()
        
        return result.rowcount > 0
    except Exception:
        db.rollback()
        return False


def cleanup_expired_sessions(db: Session) -> int:
    """Clean up expired authentication sessions."""
    try:
        now = datetime.now(timezone.utc)
        
        # Mark expired sessions as inactive
        stmt = (
            update(AuthSession)
            .where(
                and_(
                    AuthSession.access_token_expires_at < now,
                    AuthSession.is_active == True
                )
            )
            .values(
                is_active=False,
                updated_at=now
            )
        )
        
        result = db.execute(stmt)
        db.commit()
        
        return result.rowcount
    except Exception:
        db.rollback()
        return 0


def login_user(
    db: Session, 
    login_data: LoginRequestSchema,
    ip_address: Optional[str] = None,
    user_agent: Optional[str] = None
) -> Tuple[Optional[LoginResponseSchema], str]:
    """
    Login user with email and password.
    
    Args:
        db: Database session
        login_data: Login request data
        ip_address: Client IP address
        user_agent: Client user agent
        
    Returns:
        Tuple of login response and message
    """
    # Authenticate user
    user = authenticate_user(db, login_data.email, login_data.password)
    
    if not user:
        return None, "Incorrect email or password"
    
    if not user.is_user_active:
        return None, "Inactive user"
    
    # Create comprehensive user data for token payload
    user_data = {
        "user_id": user.id,
        "email": user.email,
        "username": getattr(user, 'username', None),
        "first_name": user.first_name,
        "last_name": user.last_name,
        "phone": getattr(user, 'phone', None),
        "is_active": user.is_active,
        "is_verified": user.is_verified,
        "is_superuser": getattr(user, 'is_superuser', False),
    }
    
    # Create token payload
    token_data = {
        "user": user_data
    }
    
    refresh_token_data = {
        "user": user_data
    }
    
    # Generate tokens
    access_token_expires = timedelta(minutes=settings.jwt_expires_minutes)
    refresh_token_expires = timedelta(days=settings.jwt_refresh_expires_days)
    
    access_token = jwt_manager.create_access_token(token_data, access_token_expires)
    refresh_token = jwt_manager.create_refresh_token(refresh_token_data, refresh_token_expires)
    
    # Create auth session - store actual tokens
    access_expires_at = datetime.now(timezone.utc) + access_token_expires
    refresh_expires_at = datetime.now(timezone.utc) + refresh_token_expires
    
    create_auth_session(
        db=db,
        user_id=user.id,
        access_token=access_token,
        refresh_token=refresh_token,
        access_token_expires_at=access_expires_at,
        refresh_token_expires_at=refresh_expires_at,
        ip_address=ip_address,
        user_agent=user_agent
    )
    
    # Update user last login
    user.last_login = datetime.now(timezone.utc)
    db.commit()
    
    # Create login response
    login_response = LoginResponseSchema(
        message="Authentication successful",
        user_id=user.id,
        email=user.email,
        is_active=user.is_active,
        is_verified=user.is_verified,
        access_token=access_token,
        refresh_token=refresh_token,
        token_type="bearer",
        expires_in=settings.jwt_expires_minutes * 60  # Convert to seconds
    )
    
    return login_response, "Login successful"


def register_user(
    db: Session, 
    register_data: RegisterRequestSchema
) -> Tuple[Optional[RegisterResponseSchema], str]:
    """
    Register a new user.
    
    Args:
        db: Database session
        register_data: Registration request data
        
    Returns:
        Tuple of registration response and message
    """
    # Convert register data to user create schema
    user_create_data = UserCreateSchema(
        email=register_data.email,
        first_name=register_data.first_name,
        middle_name=register_data.middle_name,
        last_name=register_data.last_name,
        phone=register_data.phone,
        is_active=True,
        is_verified=False,
    )
    
    # Create user using user controller
    user, message = create_user(db, user_create_data, register_data.password)
    
    if user is None:
        return None, message
    
    # Create registration response
    register_response = RegisterResponseSchema(
        message="Registration successful",
        user_id=user.id,
        email=user.email,
        is_active=user.is_active,
        is_verified=user.is_verified
    )
    
    return register_response, message


def get_user_by_email(db: Session, email: EmailStr) -> Optional[User]:
    """
    Get user by email address.
    
    Args:
        db: Database session
        email: User's email address
        
    Returns:
        User model if found, None otherwise
    """
    stmt = select(User).where(User.email == email)
    if hasattr(User, 'deleted_at'):
        stmt = stmt.where(User.deleted_at.is_(None))
    
    return db.execute(stmt).scalar_one_or_none()


def get_current_user_profile(user: User) -> UserProfileSchema:
    """
    Get current user profile information.
    
    Args:
        user: Current authenticated user
        
    Returns:
        User profile schema
    """
    return UserProfileSchema.model_validate(user)


def refresh_access_token(
    db: Session, 
    refresh_request: RefreshTokenRequestSchema
) -> Tuple[Optional[AuthTokenSchema], str]:
    """
    Refresh access token using refresh token.
    
    Args:
        db: Database session
        refresh_request: Refresh token request data
        
    Returns:
        Tuple of new tokens and message
    """
    # Verify refresh token
    payload = jwt_manager.verify_token(refresh_request.refresh_token)
    if not payload:
        return None, "Invalid refresh token"
    
    # Check if it's a refresh token
    if payload.get("type") != "refresh":
        return None, "Invalid token type"
    
    user_data = payload.get("user")
    
    if not user_data:
        return None, "Invalid refresh token"
    
    user_id = user_data.get("user_id")
    
    # Get user first to validate
    user_stmt = select(User).where(User.id == user_id)
    user = db.execute(user_stmt).scalar_one_or_none()
    if not user or not user.is_user_active:
        return None, "User not found or inactive"
    
    # Update user data for new token
    updated_user_data = {
        "user_id": user.id,
        "email": user.email,
        "username": getattr(user, 'username', None),
        "first_name": user.first_name,
        "last_name": user.last_name,
        "phone": getattr(user, 'phone', None),
        "is_active": user.is_active,
        "is_verified": user.is_verified,
        "is_superuser": getattr(user, 'is_superuser', False),
    }
    
    token_data = {
        "user": updated_user_data
    }
    
    access_token_expires = timedelta(minutes=settings.jwt_expires_minutes)
    new_access_token = jwt_manager.create_access_token(token_data, access_token_expires)
    
    # Return new tokens
    token_response = AuthTokenSchema(
        access_token=new_access_token,
        refresh_token=refresh_request.refresh_token,  # Keep the same refresh token
        token_type="bearer",
        expires_in=settings.jwt_expires_minutes * 60
    )
    
    return token_response, "Token refreshed successfully"


def logout_user(db: Session, user_id: Optional[int] = None) -> Tuple[LogoutResponseSchema, str]:
    """
    Logout user by revoking session(s).
    
    Args:
        db: Database session
        user_id: User ID to revoke all sessions
        
    Returns:
        Tuple of logout response and message
    """
    sessions_revoked = 0
    
    if user_id:
        # Revoke all user sessions
        sessions_revoked = revoke_all_user_sessions(db, user_id)
    
    logout_response = LogoutResponseSchema(
        message="Logout successful",
        logged_out_sessions=sessions_revoked
    )
    
    return logout_response, f"Logged out {sessions_revoked} session(s)"
