from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any, Tuple
import secrets
import sys

# Add shared modules to path
sys.path.append('/app')

from shared.security import (
    verify_password, 
    get_password_hash, 
    create_access_token, 
    create_refresh_token,
    generate_reset_token
)
from shared.kafka_client import kafka_client, EventTypes, Topics
from shared.redis_client import redis_client
from shared.config import settings

from app.models.user import User, UserStatus
from app.models.auth_session import AuthSession
from app.models.refresh_token import RefreshToken
from app.schemas.auth import LoginRequest, RegisterRequest
from bson import ObjectId

class AuthService:
    
    @staticmethod
    async def register_user(register_data: RegisterRequest) -> Tuple[User, str]:
        """Register a new user"""
        # Check if user already exists
        existing_user = await User.find_one(
            {"$or": [{"email": register_data.email}, {"username": register_data.username}]}
        )
        
        if existing_user:
            if existing_user.email == register_data.email:
                raise ValueError("Email already registered")
            else:
                raise ValueError("Username already taken")
        
        # Generate email verification token
        verification_token = generate_reset_token()
        verification_expires = datetime.utcnow() + timedelta(hours=24)
        
        # Create new user
        user = User(
            email=register_data.email,
            username=register_data.username.lower(),
            password_hash=get_password_hash(register_data.password),
            status=UserStatus.PENDING_VERIFICATION,
            email_verification_token=verification_token,
            email_verification_expires=verification_expires
        )
        
        await user.save()
        
        # Assign default role to user
        try:
            from shared.http_client import http_client
            
            # Call account service to assign default role
            response = await http_client.post(
                f"{settings.account_service_url}/accounts/roles/assign-default",
                json={"user_id": str(user.id)}
            )
            
            if response.status_code != 200:
                print(f"Warning: Failed to assign default role to user {user.username}: HTTP {response.status_code}")
        except Exception as e:
            import logging
            logger = logging.getLogger("auth_service")
            logger.warning(f"Failed to assign default role to user {user.username}: {e}")
        
        # Send registration event to Kafka
        await kafka_client.send_message(
            Topics.AUTH_EVENTS,
            {
                "event_type": EventTypes.USER_REGISTERED,
                "user_id": str(user.id),
                "email": user.email,
                "username": user.username,
                "timestamp": datetime.utcnow().isoformat()
            },
            key=str(user.id)
        )
        
        return user, verification_token

    @staticmethod
    async def authenticate_user(login_data: LoginRequest, ip_address: str, user_agent: str) -> Dict[str, Any]:
        """Authenticate user and return tokens"""
        # Find user by email
        user = await User.find_one(User.email == login_data.email)
        
        if not user:
            raise ValueError("Invalid email or password")
        
        # Check if user can login
        if not user.can_login():
            if user.is_locked():
                raise ValueError("Account is temporarily locked due to multiple failed login attempts")
            else:
                raise ValueError("Account is not active")
        
        # Verify password
        if not verify_password(login_data.password, user.password_hash):
            await user.increment_failed_attempts()
            raise ValueError("Invalid email or password")
        
        # Reset failed attempts on successful login
        await user.reset_failed_attempts()
        
        # Create tokens
        token_data = {
            "sub": str(user.id),
            "email": user.email,
            "username": user.username
        }
        
        # Create access token
        access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
        access_token = create_access_token(token_data, expires_delta=access_token_expires)
        
        # Create refresh token
        refresh_token = create_refresh_token(token_data)
        
        # After tokens are created, fetch user roles from account service using the valid access token
        roles = []
        try:
            from shared.http_client import http_client
            
            response = await http_client.get(
                f"{settings.account_service_url}/accounts/me",
                headers={"Authorization": f"Bearer {access_token}"}
            )
            
            if response.status_code == 200:
                user_data = await response.json()
                if "roles" in user_data:
                    roles = user_data["roles"]
                    print(f"Successfully fetched roles for user {user.username}: {roles}")
            else:
                print(f"Failed to fetch roles for user {user.username}: HTTP {response.status_code}")
        except Exception as e:
            import logging
            logger = logging.getLogger("auth_service")
            logger.warning(f"Failed to fetch user roles during login for user {user.username}: {e}")
            print(f"Warning: Could not fetch roles for user {user.username}, using default role")
        
        if not roles:
            roles = ["user"]
            print(f"Using default role 'user' for {user.username}")
        
        # Create session
        session_token = secrets.token_urlsafe(32)
        session = await AuthSession.create_session(
            user_id=user.id,
            session_token=session_token,
            device_info=login_data.device_info,
            ip_address=ip_address,
            user_agent=user_agent,
            expires_in_hours=24 if not login_data.remember_me else 24*7
        )
        
        # Store refresh token in database
        await RefreshToken.create_token(
            user_id=user.id,
            token=refresh_token,
            session_id=session.id,
            ip_address=ip_address,
            user_agent=user_agent,
            expires_in_days=settings.refresh_token_expire_days
        )
        
        # Cache user data in Redis
        user_cache_data = {
            "id": str(user.id),
            "email": user.email,
            "username": user.username,
            "status": user.status.value,
            "email_verified": user.email_verified,
            "roles": roles
        }
        await redis_client.set(
            f"user:{user.id}",
            user_cache_data,
            expire=settings.access_token_expire_minutes * 60
        )
        
        # Send login event to Kafka
        await kafka_client.send_message(
            Topics.AUTH_EVENTS,
            {
                "event_type": EventTypes.USER_LOGGED_IN,
                "user_id": str(user.id),
                "email": user.email,
                "username": user.username,
                "ip_address": ip_address,
                "user_agent": user_agent,
                "timestamp": datetime.utcnow().isoformat()
            },
            key=str(user.id)
        )
        
        return {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "bearer",
            "expires_in": settings.access_token_expire_minutes * 60,
            "user_id": str(user.id),
            "email": user.email,
            "username": user.username,
            "roles": roles
        }

    @staticmethod
    async def refresh_access_token(refresh_token: str) -> Dict[str, str]:
        """Refresh access token using refresh token"""
        # Find refresh token in database
        token_doc = await RefreshToken.find_one(RefreshToken.token == refresh_token)
        
        if not token_doc or not token_doc.is_valid():
            raise ValueError("Invalid or expired refresh token")
        
        # Get user
        user = await User.get(token_doc.user_id)
        if not user or not user.is_active():
            raise ValueError("User not found or not active")
        
        # Mark refresh token as used
        await token_doc.use_token()
        
        # Create new access token
        token_data = {
            "sub": str(user.id),
            "email": user.email,
            "username": user.username
        }
        
        access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
        access_token = create_access_token(token_data, expires_delta=access_token_expires)
        
        return {
            "access_token": access_token,
            "token_type": "bearer",
            "expires_in": settings.access_token_expire_minutes * 60
        }

    @staticmethod
    async def logout_user(user_id: str, refresh_token: Optional[str] = None, logout_all: bool = False):
        """Logout user by revoking tokens"""
        if logout_all:
            # Revoke all refresh tokens for user
            await RefreshToken.revoke_all_user_tokens(ObjectId(user_id), "logout_all")
            
            # Revoke all auth sessions
            sessions = await AuthSession.find(
                AuthSession.user_id == ObjectId(user_id),
                AuthSession.is_active == True
            ).to_list()
            
            for session in sessions:
                await session.revoke("logout_all")
            
        elif refresh_token:
            # Revoke specific refresh token
            token_doc = await RefreshToken.find_one(RefreshToken.token == refresh_token)
            if token_doc:
                await token_doc.revoke("logout")
                
                # Revoke associated session
                if token_doc.session_id:
                    session = await AuthSession.get(token_doc.session_id)
                    if session:
                        await session.revoke("logout")
        
        # Send logout event to Kafka
        user = await User.get(ObjectId(user_id))
        if user:
            await kafka_client.send_message(
                Topics.AUTH_EVENTS,
                {
                    "event_type": EventTypes.USER_LOGGED_OUT,
                    "user_id": user_id,
                    "email": user.email,
                    "username": user.username,
                    "logout_all": logout_all,
                    "timestamp": datetime.utcnow().isoformat()
                },
                key=user_id
            )
        
        # Clear user cache
        await redis_client.delete(f"user:{user_id}")

    @staticmethod
    async def verify_email(token: str) -> bool:
        """Verify user email with token"""
        user = await User.find_one(User.email_verification_token == token)
        
        if not user:
            raise ValueError("Invalid verification token")
        
        if user.email_verification_expires and datetime.utcnow() > user.email_verification_expires:
            raise ValueError("Verification token has expired")
        
        # Update user status
        user.email_verified = True
        user.status = UserStatus.ACTIVE
        user.email_verification_token = None
        user.email_verification_expires = None
        user.updated_at = datetime.utcnow()
        
        await user.save()
        
        return True

    @staticmethod
    async def request_password_reset(email: str) -> str:
        """Request password reset"""
        user = await User.find_one(User.email == email)
        
        if not user:
            # Don't reveal if email exists
            return "If the email exists, a reset link has been sent"
        
        # Generate reset token
        reset_token = generate_reset_token()
        reset_expires = datetime.utcnow() + timedelta(hours=1)
        
        user.password_reset_token = reset_token
        user.password_reset_expires = reset_expires
        user.updated_at = datetime.utcnow()
        
        await user.save()
        
        # TODO: Send email with reset token
        
        return reset_token

    @staticmethod
    async def reset_password(token: str, new_password: str) -> bool:
        """Reset password with token"""
        user = await User.find_one(User.password_reset_token == token)
        
        if not user:
            raise ValueError("Invalid reset token")
        
        if user.password_reset_expires and datetime.utcnow() > user.password_reset_expires:
            raise ValueError("Reset token has expired")
        
        # Update password
        user.password_hash = get_password_hash(new_password)
        user.password_reset_token = None
        user.password_reset_expires = None
        user.password_changed_at = datetime.utcnow()
        user.updated_at = datetime.utcnow()
        
        await user.save()
        
        # Revoke all existing tokens for security
        await RefreshToken.revoke_all_user_tokens(user.id, "password_reset")
        
        # Send password changed event
        await kafka_client.send_message(
            Topics.AUTH_EVENTS,
            {
                "event_type": EventTypes.PASSWORD_CHANGED,
                "user_id": str(user.id),
                "email": user.email,
                "username": user.username,
                "timestamp": datetime.utcnow().isoformat()
            },
            key=str(user.id)
        )
        
        return True

    @staticmethod
    async def change_password(user_id: str, current_password: str, new_password: str) -> bool:
        """Change user password"""
        user = await User.get(ObjectId(user_id))
        
        if not user:
            raise ValueError("User not found")
        
        # Verify current password
        if not verify_password(current_password, user.password_hash):
            raise ValueError("Current password is incorrect")
        
        # Update password
        user.password_hash = get_password_hash(new_password)
        user.password_changed_at = datetime.utcnow()
        user.updated_at = datetime.utcnow()
        
        await user.save()
        
        # Send password changed event
        await kafka_client.send_message(
            Topics.AUTH_EVENTS,
            {
                "event_type": EventTypes.PASSWORD_CHANGED,
                "user_id": user_id,
                "email": user.email,
                "username": user.username,
                "timestamp": datetime.utcnow().isoformat()
            },
            key=user_id
        )
        
        return True
