"""
Authentication domain service
"""
from datetime import datetime, timedelta
from typing import Optional, Tuple
from uuid import UUID

from app.core.exceptions.auth import (
    InvalidCredentialsError,
    UserNotFoundError,
    UserInactiveError,
    UserLockedError,
    EmailNotVerifiedError,
    TokenExpiredError,
    InvalidTokenError
)
from app.core.security.jwt import JWTHandler
from app.core.security.password import PasswordHandler
from app.domain.entities.user import User, UserStatus
from app.domain.repositories.user_repository import UserRepository
from app.schemas.auth import TokenResponse, LoginRequest


class AuthService:
    """Authentication service"""
    
    def __init__(
        self,
        user_repository: UserRepository,
        password_handler: PasswordHandler,
        jwt_handler: JWTHandler
    ):
        self.user_repository = user_repository
        self.password_handler = password_handler
        self.jwt_handler = jwt_handler
    
    async def authenticate_user(self, login_request: LoginRequest) -> Tuple[User, TokenResponse]:
        """Authenticate user and return tokens"""
        # Find user by email or username
        user = await self.user_repository.find_by_email_or_username(
            login_request.email_or_username
        )
        
        if not user:
            raise UserNotFoundError("User not found")
        
        # Check if user is locked
        if user.is_locked:
            raise UserLockedError("Account is temporarily locked")
        
        # Verify password
        if not self.password_handler.verify_password(
            login_request.password, user.hashed_password
        ):
            # Increment failed login attempts
            user.increment_failed_login()
            await self.user_repository.update(user)
            raise InvalidCredentialsError("Invalid credentials")
        
        # Check user status
        if user.status == UserStatus.SUSPENDED:
            raise UserInactiveError("Account is suspended")
        
        if user.status == UserStatus.INACTIVE:
            raise UserInactiveError("Account is inactive")
        
        if user.status == UserStatus.PENDING_VERIFICATION and not user.is_email_verified:
            raise EmailNotVerifiedError("Email verification required")
        
        # Update last login
        user.update_last_login()
        await self.user_repository.update(user)
        
        # Generate tokens
        tokens = await self._generate_tokens(user)
        
        return user, tokens
    
    async def refresh_token(self, refresh_token: str) -> TokenResponse:
        """Refresh access token using refresh token"""
        try:
            payload = self.jwt_handler.decode_token(refresh_token)
            user_id = UUID(payload.get("sub"))
            token_type = payload.get("type")
            
            if token_type != "refresh":
                raise InvalidTokenError("Invalid token type")
            
            user = await self.user_repository.find_by_id(user_id)
            if not user or not user.is_active:
                raise UserNotFoundError("User not found or inactive")
            
            return await self._generate_tokens(user)
            
        except Exception as e:
            raise InvalidTokenError("Invalid refresh token") from e
    
    async def logout(self, user_id: UUID, token: str) -> None:
        """Logout user and invalidate token"""
        # Add token to blacklist (implement token blacklisting)
        # For now, we'll just update user's last activity
        user = await self.user_repository.find_by_id(user_id)
        if user:
            user.last_activity_at = datetime.utcnow()
            await self.user_repository.update(user)
    
    async def verify_email(self, token: str) -> User:
        """Verify user email with token"""
        user = await self.user_repository.find_by_email_verification_token(token)
        if not user:
            raise InvalidTokenError("Invalid verification token")
        
        user.verify_email()
        await self.user_repository.update(user)
        
        return user
    
    async def request_password_reset(self, email: str) -> str:
        """Request password reset and return reset token"""
        user = await self.user_repository.find_by_email(email)
        if not user:
            # Don't reveal if email exists
            return "reset_token_placeholder"
        
        # Generate reset token
        reset_token = self.jwt_handler.generate_reset_token()
        user.password_reset_token = reset_token
        user.password_reset_expires = datetime.utcnow() + timedelta(hours=1)
        
        await self.user_repository.update(user)
        
        return reset_token
    
    async def reset_password(self, token: str, new_password: str) -> User:
        """Reset user password with token"""
        user = await self.user_repository.find_by_password_reset_token(token)
        if not user:
            raise InvalidTokenError("Invalid reset token")
        
        if user.password_reset_expires and user.password_reset_expires < datetime.utcnow():
            raise TokenExpiredError("Reset token has expired")
        
        # Update password
        user.hashed_password = self.password_handler.hash_password(new_password)
        user.password_reset_token = None
        user.password_reset_expires = None
        user.failed_login_attempts = 0
        user.locked_until = None
        user.updated_at = datetime.utcnow()
        
        await self.user_repository.update(user)
        
        return user
    
    async def change_password(
        self, user_id: UUID, current_password: str, new_password: str
    ) -> User:
        """Change user password"""
        user = await self.user_repository.find_by_id(user_id)
        if not user:
            raise UserNotFoundError("User not found")
        
        # Verify current password
        if not self.password_handler.verify_password(current_password, user.hashed_password):
            raise InvalidCredentialsError("Current password is incorrect")
        
        # Update password
        user.hashed_password = self.password_handler.hash_password(new_password)
        user.updated_at = datetime.utcnow()
        
        await self.user_repository.update(user)
        
        return user
    
    async def _generate_tokens(self, user: User) -> TokenResponse:
        """Generate access and refresh tokens for user"""
        access_token = self.jwt_handler.create_access_token(
            subject=str(user.id),
            additional_claims={
                "email": user.email,
                "roles": user.roles,
                "permissions": user.permissions
            }
        )
        
        refresh_token = self.jwt_handler.create_refresh_token(
            subject=str(user.id)
        )
        
        return TokenResponse(
            access_token=access_token,
            refresh_token=refresh_token,
            token_type="bearer",
            expires_in=self.jwt_handler.access_token_expire_minutes * 60
        )
