"""
Enhanced Authentication Service
Updated to use repository pattern with dependency injection and improved error handling
"""

import uuid
from datetime import datetime, timedelta, timezone
from typing import Dict, Any, Optional

from fastapi import HTTPException, status
import structlog

from app.repositories import UserRepository, TokenRepository
from app.schemas.auth import UserRegistration, UserLogin, TokenResponse, UserResponse
from app.models.users import User, RefreshToken
from app.core.security import SecurityManager
from app.services.messaging import publish_user_registered, publish_user_login
from shared.database.unit_of_work import UnitOfWork
from shared.database.transactions import transactional
from shared.database.exceptions import DatabaseError, ValidationError, DuplicateRecordError

logger = structlog.get_logger()


# Legacy compatibility alias
AuthService = None  # Will be set at the end of the file


class EnhancedAuthService:
    """Enhanced authentication service using repository pattern"""
    
    def __init__(self, database_manager):
        """Initialize service with database manager"""
        self.database_manager = database_manager
    
    async def register_user(
        self, 
        user_data: UserRegistration
    ) -> TokenResponse:
        """Register a new user using repository pattern"""
        try:
            async with self.database_manager.get_session() as db_session:
                async with UnitOfWork(db_session) as uow:
                    # Register repositories
                    user_repo = uow.register_repository("users", UserRepository, User)
                    token_repo = uow.register_repository("tokens", TokenRepository, RefreshToken)
                    
                    # Check if user already exists
                    existing_user = await user_repo.get_by_email(user_data.email)
                    if existing_user:
                        raise DuplicateRecordError("User with this email already exists")
                    
                    # Validate password strength
                    if not SecurityManager.validate_password(user_data.password):
                        raise ValueError("Password does not meet security requirements")
                    
                    # Create user data
                    # Default to admin role for first-time registrations during onboarding flow
                    # Users creating their own bakery should have admin privileges
                    user_role = user_data.role if user_data.role else "admin"
                    hashed_password = SecurityManager.hash_password(user_data.password)
                    
                    create_data = {
                        "email": user_data.email,
                        "full_name": user_data.full_name,
                        "hashed_password": hashed_password,
                        "is_active": True,
                        "is_verified": False,
                        "role": user_role
                    }
                    
                    # Create user using repository
                    new_user = await user_repo.create_user(create_data)
                    
                    logger.debug("User created with repository pattern",
                               user_id=new_user.id,
                               email=user_data.email,
                               role=user_role)
                    
                    # Create tokens with different payloads
                    access_token_data = {
                        "user_id": str(new_user.id),
                        "email": new_user.email,
                        "full_name": new_user.full_name,
                        "is_verified": new_user.is_verified,
                        "is_active": new_user.is_active,
                        "role": new_user.role,
                        "type": "access"
                    }
                    
                    refresh_token_data = {
                        "user_id": str(new_user.id),
                        "email": new_user.email,
                        "type": "refresh"
                    }
                    
                    # Generate tokens
                    access_token = SecurityManager.create_access_token(user_data=access_token_data)
                    refresh_token_value = SecurityManager.create_refresh_token(user_data=refresh_token_data)
                    
                    # Store refresh token using repository
                    token_data = {
                        "user_id": str(new_user.id),
                        "token": refresh_token_value,
                        "expires_at": datetime.now(timezone.utc) + timedelta(days=30),
                        "is_revoked": False
                    }
                    
                    await token_repo.create_token(token_data)
                    
                    # Commit transaction
                    await uow.commit()
                    
                    # Publish registration event (non-blocking)
                    try:
                        await publish_user_registered({
                            "user_id": str(new_user.id),
                            "email": new_user.email,
                            "full_name": new_user.full_name,
                            "role": new_user.role,
                            "registered_at": datetime.now(timezone.utc).isoformat()
                        })
                    except Exception as e:
                        logger.warning("Failed to publish registration event", error=str(e))
                    
                    logger.info("User registered successfully using repository pattern",
                               user_id=new_user.id,
                               email=user_data.email)
                    
                    from app.schemas.auth import UserData
                    return TokenResponse(
                        access_token=access_token,
                        refresh_token=refresh_token_value,
                        token_type="bearer",
                        expires_in=1800,
                        user=UserData(
                            id=str(new_user.id),
                            email=new_user.email,
                            full_name=new_user.full_name,
                            is_active=new_user.is_active,
                            is_verified=new_user.is_verified,
                            created_at=new_user.created_at.isoformat() if new_user.created_at else datetime.now(timezone.utc).isoformat(),
                            role=new_user.role
                        )
                    )
                    
        except (ValidationError, DuplicateRecordError):
            raise
        except Exception as e:
            logger.error("Registration failed using repository pattern",
                        email=user_data.email,
                        error=str(e))
            raise DatabaseError(f"Registration failed: {str(e)}")
    
    async def login_user(
        self, 
        login_data: UserLogin
    ) -> TokenResponse:
        """Login user using repository pattern"""
        try:
            async with self.database_manager.get_session() as db_session:
                async with UnitOfWork(db_session) as uow:
                    # Register repositories
                    user_repo = uow.register_repository("users", UserRepository, User)
                    token_repo = uow.register_repository("tokens", TokenRepository, RefreshToken)
                    
                    # Authenticate user using repository
                    user = await user_repo.authenticate_user(login_data.email, login_data.password)
                    if not user:
                        raise HTTPException(
                            status_code=status.HTTP_401_UNAUTHORIZED,
                            detail="Invalid email or password"
                        )
                    
                    if not user.is_active:
                        raise HTTPException(
                            status_code=status.HTTP_403_FORBIDDEN,
                            detail="Account is deactivated"
                        )
                    
                    # Revoke existing refresh tokens using repository
                    await token_repo.revoke_all_user_tokens(str(user.id))
                    
                    logger.debug("Existing tokens revoked using repository pattern",
                               user_id=user.id)
                    
                    # Create tokens with different payloads
                    access_token_data = {
                        "user_id": str(user.id),
                        "email": user.email,
                        "full_name": user.full_name,
                        "is_verified": user.is_verified,
                        "is_active": user.is_active,
                        "role": user.role,
                        "type": "access"
                    }
                    
                    refresh_token_data = {
                        "user_id": str(user.id),
                        "email": user.email,
                        "type": "refresh",
                        "jti": str(uuid.uuid4())
                    }
                    
                    # Generate tokens
                    access_token = SecurityManager.create_access_token(user_data=access_token_data)
                    refresh_token_value = SecurityManager.create_refresh_token(user_data=refresh_token_data)
                    
                    # Store refresh token using repository
                    token_data = {
                        "user_id": str(user.id),
                        "token": refresh_token_value,
                        "expires_at": datetime.now(timezone.utc) + timedelta(days=30),
                        "is_revoked": False
                    }
                    
                    await token_repo.create_token(token_data)
                    
                    # Update last login using repository
                    await user_repo.update_last_login(str(user.id))
                    
                    # Commit transaction
                    await uow.commit()
                    
                    # Publish login event (non-blocking)
                    try:
                        await publish_user_login({
                            "user_id": str(user.id),
                            "email": user.email,
                            "login_at": datetime.now(timezone.utc).isoformat()
                        })
                    except Exception as e:
                        logger.warning("Failed to publish login event", error=str(e))
                    
                    logger.info("User logged in successfully using repository pattern",
                               user_id=user.id,
                               email=login_data.email)
                    
                    from app.schemas.auth import UserData
                    return TokenResponse(
                        access_token=access_token,
                        refresh_token=refresh_token_value,
                        token_type="bearer",
                        expires_in=1800,
                        user=UserData(
                            id=str(user.id),
                            email=user.email,
                            full_name=user.full_name,
                            is_active=user.is_active,
                            is_verified=user.is_verified,
                            created_at=user.created_at.isoformat() if user.created_at else datetime.now(timezone.utc).isoformat(),
                            role=user.role
                        )
                    )
                    
        except HTTPException:
            raise
        except Exception as e:
            logger.error("Login failed using repository pattern",
                        email=login_data.email,
                        error=str(e))
            raise DatabaseError(f"Login failed: {str(e)}")
    
    async def logout_user(self, user_id: str, refresh_token: str) -> bool:
        """Logout user using repository pattern"""
        try:
            async with self.database_manager.get_session() as session:
                token_repo = TokenRepository(RefreshToken, session)
                
                # Revoke specific refresh token using repository
                success = await token_repo.revoke_token(user_id, refresh_token)
                
                if success:
                    logger.info("User logged out successfully using repository pattern",
                               user_id=user_id)
                    return True
                
                return False
                
        except Exception as e:
            logger.error("Logout failed using repository pattern",
                        user_id=user_id,
                        error=str(e))
            return False
    
    async def refresh_access_token(self, refresh_token: str) -> Dict[str, Any]:
        """Refresh access token using repository pattern"""
        try:
            # Verify refresh token
            payload = SecurityManager.decode_token(refresh_token)
            user_id = payload.get("user_id")
            
            if not user_id:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="Invalid refresh token"
                )
            
            async with self.database_manager.get_session() as session:
                user_repo = UserRepository(User, session)
                token_repo = TokenRepository(RefreshToken, session)
                
                # Validate refresh token using repository
                is_valid = await token_repo.validate_refresh_token(refresh_token, user_id)
                if not is_valid:
                    raise HTTPException(
                        status_code=status.HTTP_401_UNAUTHORIZED,
                        detail="Invalid or expired refresh token"
                    )
                
                # Get user using repository
                user = await user_repo.get_by_id(user_id)
                if not user or not user.is_active:
                    raise HTTPException(
                        status_code=status.HTTP_401_UNAUTHORIZED,
                        detail="User not found or inactive"
                    )
                
                # Create new access token
                access_token_data = {
                    "user_id": str(user.id),
                    "email": user.email,
                    "full_name": user.full_name,
                    "is_verified": user.is_verified,
                    "is_active": user.is_active,
                    "role": user.role,
                    "type": "access"
                }
                
                new_access_token = SecurityManager.create_access_token(user_data=access_token_data)
                
                logger.debug("Access token refreshed successfully using repository pattern",
                           user_id=user_id)
                
                return {
                    "access_token": new_access_token,
                    "token_type": "bearer",
                    "expires_in": 1800
                }
                
        except HTTPException:
            raise
        except Exception as e:
            logger.error("Token refresh failed using repository pattern", error=str(e))
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Token refresh failed"
            )
    
    async def verify_user_token(self, token: str) -> Dict[str, Any]:
        """Verify access token and return user info"""
        try:
            payload = SecurityManager.verify_token(token)
            if not payload:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="Invalid token"
                )
            
            return payload
            
        except Exception as e:
            logger.error("Token verification error using repository pattern", error=str(e))
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid token"
            )
    
    async def get_user_profile(self, user_id: str) -> Optional[UserResponse]:
        """Get user profile using repository pattern"""
        try:
            async with self.database_manager.get_session() as session:
                user_repo = UserRepository(User, session)
                
                user = await user_repo.get_by_id(user_id)
                if not user:
                    return None
                
                return UserResponse(
                    id=str(user.id),
                    email=user.email,
                    full_name=user.full_name,
                    is_active=user.is_active,
                    is_verified=user.is_verified,
                    created_at=user.created_at,
                    role=user.role
                )
                
        except Exception as e:
            logger.error("Failed to get user profile using repository pattern",
                        user_id=user_id,
                        error=str(e))
            return None
    
    async def update_user_profile(
        self, 
        user_id: str, 
        update_data: Dict[str, Any]
    ) -> Optional[UserResponse]:
        """Update user profile using repository pattern"""
        try:
            async with self.database_manager.get_session() as session:
                user_repo = UserRepository(User, session)
                
                updated_user = await user_repo.update(user_id, update_data)
                if not updated_user:
                    return None
                
                logger.info("User profile updated using repository pattern",
                           user_id=user_id,
                           updated_fields=list(update_data.keys()))
                
                return UserResponse(
                    id=str(updated_user.id),
                    email=updated_user.email,
                    full_name=updated_user.full_name,
                    is_active=updated_user.is_active,
                    is_verified=updated_user.is_verified,
                    created_at=updated_user.created_at,
                    role=updated_user.role
                )
                
        except Exception as e:
            logger.error("Failed to update user profile using repository pattern",
                        user_id=user_id,
                        error=str(e))
            raise DatabaseError(f"Failed to update profile: {str(e)}")
    
    async def change_password(
        self, 
        user_id: str, 
        old_password: str, 
        new_password: str
    ) -> bool:
        """Change user password using repository pattern"""
        try:
            async with self.database_manager.get_session() as session:
                user_repo = UserRepository(User, session)
                token_repo = TokenRepository(RefreshToken, session)
                
                # Get user and verify old password
                user = await user_repo.get_by_id(user_id)
                if not user:
                    raise HTTPException(
                        status_code=status.HTTP_404_NOT_FOUND,
                        detail="User not found"
                    )
                
                if not SecurityManager.verify_password(old_password, user.hashed_password):
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST,
                        detail="Invalid old password"
                    )
                
                # Validate new password strength
                if not SecurityManager.validate_password(new_password):
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST,
                        detail="New password does not meet security requirements"
                    )
                
                # Hash new password and update
                new_hashed_password = SecurityManager.hash_password(new_password)
                await user_repo.update(user_id, {"hashed_password": new_hashed_password})
                
                # Revoke all existing tokens for security
                await token_repo.revoke_all_user_tokens(user_id)
                
                logger.info("Password changed successfully using repository pattern",
                           user_id=user_id)
                
                return True
                
        except HTTPException:
            raise
        except Exception as e:
            logger.error("Failed to change password using repository pattern",
                        user_id=user_id,
                        error=str(e))
            raise DatabaseError(f"Failed to change password: {str(e)}")
    
    async def verify_user_email(self, user_id: str, verification_token: str) -> bool:
        """Verify user email using repository pattern"""
        try:
            async with self.database_manager.get_session() as session:
                user_repo = UserRepository(User, session)
                
                # In a real implementation, you'd verify the verification_token
                # For now, just mark user as verified
                updated_user = await user_repo.update(user_id, {"is_verified": True})
                
                if updated_user:
                    logger.info("User email verified using repository pattern",
                               user_id=user_id)
                    return True
                
                return False
                
        except Exception as e:
            logger.error("Failed to verify email using repository pattern",
                        user_id=user_id,
                        error=str(e))
            return False
    
    async def deactivate_user(self, user_id: str, admin_user_id: str) -> bool:
        """Deactivate user account using repository pattern"""
        try:
            async with self.database_manager.get_session() as session:
                user_repo = UserRepository(User, session)
                token_repo = TokenRepository(RefreshToken, session)
                
                # Update user status
                updated_user = await user_repo.update(user_id, {"is_active": False})
                if not updated_user:
                    return False
                
                # Revoke all tokens
                await token_repo.revoke_all_user_tokens(user_id)
                
                logger.info("User deactivated using repository pattern",
                           user_id=user_id,
                           admin_user_id=admin_user_id)
                
                return True
                
        except Exception as e:
            logger.error("Failed to deactivate user using repository pattern",
                        user_id=user_id,
                        error=str(e))
            return False


# Legacy compatibility - alias EnhancedAuthService as AuthService
AuthService = EnhancedAuthService


class EnhancedUserService(EnhancedAuthService):
    """User service alias for backward compatibility"""
    pass