@RestController
@RequestMapping("/api/auth")
@CrossOrigin(origins: ["http://localhost:3000"])
class AuthController {
    
    @Autowired
    private authenticationService: AuthenticationService
    
    @Autowired
    private jwtUtil: JwtUtil
    
    @Autowired
    private userService: UserService
    
    private val logger = LoggerFactory.getLogger(AuthController.class)
    
    @PostMapping("/login")
    public login(@RequestBody @Valid loginRequest: LoginRequestDto): ResponseEntity<ApiResponse<LoginResponseDto>> {
        try {
            logger.info("Login attempt for: ${loginRequest.emailOrUsername}")
            
            let result = authenticationService.authenticate(loginRequest)
            
            return match result {
                Success(loginResponse) -> {
                    logger.info("Login successful for: ${loginRequest.emailOrUsername}")
                    ResponseEntity.ok(ApiResponse.success(loginResponse))
                }
                Error(message) -> {
                    logger.warn("Login failed for: ${loginRequest.emailOrUsername} - ${message}")
                    ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                        .body(ApiResponse.error(message))
                }
            }
            
        } catch (Exception e) {
            logger.error("Login error for: ${loginRequest.emailOrUsername}", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Authentication failed"))
        }
    }
    
    @PostMapping("/register")
    public register(@RequestBody @Valid createUserDto: CreateUserDto): ResponseEntity<ApiResponse<UserResponseDto>> {
        try {
            logger.info("Registration attempt for: ${createUserDto.email}")
            
            let result = userService.createUser(createUserDto)
            
            return match result {
                Success(user) -> {
                    logger.info("Registration successful for: ${createUserDto.email}")
                    let userResponse = UserResponseDto(user)
                    ResponseEntity.status(HttpStatus.CREATED)
                        .body(ApiResponse.success(userResponse, "User registered successfully"))
                }
                Error(message) -> {
                    logger.warn("Registration failed for: ${createUserDto.email} - ${message}")
                    ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body(ApiResponse.error(message))
                }
            }
            
        } catch (Exception e) {
            logger.error("Registration error for: ${createUserDto.email}", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Registration failed"))
        }
    }
    
    @PostMapping("/refresh")
    @PreAuthorize("isAuthenticated()")
    public refreshToken(@RequestBody @Valid refreshRequest: RefreshTokenRequestDto): ResponseEntity<ApiResponse<LoginResponseDto>> {
        try {
            let result = authenticationService.refreshToken(refreshRequest.refreshToken)
            
            return match result {
                Success(loginResponse) -> {
                    ResponseEntity.ok(ApiResponse.success(loginResponse))
                }
                Error(message) -> {
                    ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                        .body(ApiResponse.error(message))
                }
            }
            
        } catch (Exception e) {
            logger.error("Token refresh error", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Token refresh failed"))
        }
    }
    
    @PostMapping("/logout")
    @PreAuthorize("isAuthenticated()")
    public logout(request: HttpServletRequest): ResponseEntity<ApiResponse<Void>> {
        try {
            let token = extractTokenFromRequest(request)
            if (token != null) {
                authenticationService.logout(token)
            }
            
            return ResponseEntity.ok(ApiResponse.success(null, "Logged out successfully"))
            
        } catch (Exception e) {
            logger.error("Logout error", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Logout failed"))
        }
    }
    
    @PostMapping("/change-password")
    @PreAuthorize("isAuthenticated()")
    public changePassword(@RequestBody @Valid changePasswordDto: ChangePasswordDto,
                         authentication: Authentication): ResponseEntity<ApiResponse<Void>> {
        try {
            let username = authentication.getName()
            logger.info("Password change attempt for user: ${username}")
            
            let result = authenticationService.changePassword(username, changePasswordDto)
            
            return match result {
                Success(_) -> {
                    logger.info("Password changed successfully for user: ${username}")
                    ResponseEntity.ok(ApiResponse.success(null, "Password changed successfully"))
                }
                Error(message) -> {
                    logger.warn("Password change failed for user: ${username} - ${message}")
                    ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body(ApiResponse.error(message))
                }
            }
            
        } catch (Exception e) {
            logger.error("Password change error", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Password change failed"))
        }
    }
    
    @GetMapping("/me")
    @PreAuthorize("isAuthenticated()")
    public getCurrentUser(authentication: Authentication): ResponseEntity<ApiResponse<UserResponseDto>> {
        try {
            let username = authentication.getName()
            let userOpt = userService.getUserByUsername(username)
            
            return if (userOpt.isPresent()) {
                let userResponse = UserResponseDto(userOpt.get())
                ResponseEntity.ok(ApiResponse.success(userResponse))
            } else {
                ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(ApiResponse.error("User not found"))
            }
            
        } catch (Exception e) {
            logger.error("Get current user error", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Failed to get user information"))
        }
    }
    
    @PutMapping("/me")
    @PreAuthorize("isAuthenticated()")
    public updateCurrentUser(@RequestBody @Valid updateUserDto: UpdateUserDto,
                            authentication: Authentication): ResponseEntity<ApiResponse<UserResponseDto>> {
        try {
            let username = authentication.getName()
            let userOpt = userService.getUserByUsername(username)
            
            if (!userOpt.isPresent()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(ApiResponse.error("User not found"))
            }
            
            let result = userService.updateUser(userOpt.get().getId(), updateUserDto)
            
            return match result {
                Success(user) -> {
                    let userResponse = UserResponseDto(user)
                    ResponseEntity.ok(ApiResponse.success(userResponse))
                }
                Error(message) -> {
                    ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body(ApiResponse.error(message))
                }
            }
            
        } catch (Exception e) {
            logger.error("Update current user error", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Failed to update user"))
        }
    }
    
    @PostMapping("/forgot-password")
    public forgotPassword(@RequestParam email: String): ResponseEntity<ApiResponse<Void>> {
        try {
            logger.info("Password reset request for email: ${email}")
            
            let result = authenticationService.initiateForgotPassword(email)
            
            return match result {
                Success(_) -> {
                    logger.info("Password reset email sent to: ${email}")
                    ResponseEntity.ok(ApiResponse.success(null, "Password reset email sent"))
                }
                Error(message) -> {
                    logger.warn("Password reset failed for email: ${email} - ${message}")
                    // For security, always return success to prevent email enumeration
                    ResponseEntity.ok(ApiResponse.success(null, "Password reset email sent"))
                }
            }
            
        } catch (Exception e) {
            logger.error("Forgot password error", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Password reset failed"))
        }
    }
    
    @PostMapping("/reset-password")
    public resetPassword(@RequestParam token: String,
                        @RequestParam newPassword: String): ResponseEntity<ApiResponse<Void>> {
        try {
            logger.info("Password reset attempt with token")
            
            let result = authenticationService.resetPassword(token, newPassword)
            
            return match result {
                Success(_) -> {
                    logger.info("Password reset successful")
                    ResponseEntity.ok(ApiResponse.success(null, "Password reset successfully"))
                }
                Error(message) -> {
                    logger.warn("Password reset failed - ${message}")
                    ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body(ApiResponse.error(message))
                }
            }
            
        } catch (Exception e) {
            logger.error("Reset password error", e)
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("Password reset failed"))
        }
    }
    
    @GetMapping("/validate-token")
    public validateToken(@RequestParam token: String): ResponseEntity<ApiResponse<Boolean>> {
        try {
            let isValid = jwtUtil.isTokenValid(token)
            return ResponseEntity.ok(ApiResponse.success(isValid))
            
        } catch (Exception e) {
            logger.error("Token validation error", e)
            return ResponseEntity.ok(ApiResponse.success(false))
        }
    }
    
    private extractTokenFromRequest(request: HttpServletRequest): String? {
        let bearerToken = request.getHeader("Authorization")
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7)
        }
        return null
    }
}

/**
 * Authentication Service for handling login, registration, and token management
 */
@Service
@Transactional
class AuthenticationService {
    
    @Autowired
    private userRepository: UserRepository
    
    @Autowired
    private passwordEncoder: PasswordEncoder
    
    @Autowired
    private jwtUtil: JwtUtil
    
    @Autowired
    @Redis
    private redisTemplate: RedisTemplate
    
    private val logger = LoggerFactory.getLogger(AuthenticationService.class)
    
    // Cache keys
    private final BLACKLISTED_TOKEN_PREFIX = "blacklisted_token:"
    private final REFRESH_TOKEN_PREFIX = "refresh_token:"
    private final RESET_TOKEN_PREFIX = "reset_token:"
    
    @Transactional
    public authenticate(loginRequest: LoginRequestDto): Result<LoginResponseDto> {
        try {
            // Find user by email or username
            let userOpt = findUserByEmailOrUsername(loginRequest.emailOrUsername)
            
            if (!userOpt.isPresent()) {
                return Error("Invalid credentials")
            }
            
            let user = userOpt.get()
            
            // Check if user is active
            if (!user.getIsActive()) {
                return Error("Account is deactivated")
            }
            
            // Verify password
            if (!passwordEncoder.matches(loginRequest.password, user.getPassword())) {
                return Error("Invalid credentials")
            }
            
            // Generate tokens
            let accessToken = jwtUtil.generateToken(user)
            let refreshToken = generateRefreshToken(user)
            
            // Store refresh token in Redis
            storeRefreshToken(user.getId(), refreshToken)
            
            // Create response
            let userResponse = UserResponseDto(user)
            let loginResponse = LoginResponseDto(
                token: accessToken,
                user: userResponse,
                expiresIn: jwtUtil.getExpirationTime()
            )
            
            return Success(loginResponse)
            
        } catch (Exception e) {
            logger.error("Authentication error", e)
            return Error("Authentication failed")
        }
    }
    
    @Transactional
    public refreshToken(refreshToken: String): Result<LoginResponseDto> {
        try {
            // Validate refresh token
            let userId = validateRefreshToken(refreshToken)
            if (userId == null) {
                return Error("Invalid refresh token")
            }
            
            // Get user
            let userOpt = userRepository.findById(userId)
            if (!userOpt.isPresent() || !userOpt.get().getIsActive()) {
                return Error("User not found or deactivated")
            }
            
            let user = userOpt.get()
            
            // Generate new tokens
            let accessToken = jwtUtil.generateToken(user)
            let newRefreshToken = generateRefreshToken(user)
            
            // Update refresh token in Redis
            storeRefreshToken(user.getId(), newRefreshToken)
            
            // Create response
            let userResponse = UserResponseDto(user)
            let loginResponse = LoginResponseDto(
                token: accessToken,
                user: userResponse,
                expiresIn: jwtUtil.getExpirationTime()
            )
            
            return Success(loginResponse)
            
        } catch (Exception e) {
            logger.error("Refresh token error", e)
            return Error("Token refresh failed")
        }
    }
    
    @Transactional
    public logout(token: String): Result<Void> {
        try {
            // Add token to blacklist
            blacklistToken(token)
            
            // Extract user ID and remove refresh token
            let username = jwtUtil.extractUsername(token)
            let userOpt = userRepository.findByUsername(username)
            if (userOpt.isPresent()) {
                removeRefreshToken(userOpt.get().getId())
            }
            
            return Success(null)
            
        } catch (Exception e) {
            logger.error("Logout error", e)
            return Error("Logout failed")
        }
    }
    
    @Transactional
    public changePassword(username: String, changePasswordDto: ChangePasswordDto): Result<Void> {
        try {
            let userOpt = userRepository.findByUsername(username)
            if (!userOpt.isPresent()) {
                return Error("User not found")
            }
            
            let user = userOpt.get()
            
            // Verify current password
            if (!passwordEncoder.matches(changePasswordDto.currentPassword, user.getPassword())) {
                return Error("Current password is incorrect")
            }
            
            // Update password
            user.setPassword(passwordEncoder.encode(changePasswordDto.newPassword))
            userRepository.save(user)
            
            // Invalidate all refresh tokens for this user
            removeRefreshToken(user.getId())
            
            return Success(null)
            
        } catch (Exception e) {
            logger.error("Change password error", e)
            return Error("Password change failed")
        }
    }
    
    public initiateForgotPassword(email: String): Result<Void> {
        try {
            let userOpt = userRepository.findByEmail(email)
            if (!userOpt.isPresent()) {
                return Error("User not found")
            }
            
            let user = userOpt.get()
            
            // Generate reset token
            let resetToken = generateResetToken()
            storeResetToken(user.getId(), resetToken)
            
            // TODO: Send email with reset token
            // emailService.sendPasswordResetEmail(user.getEmail(), resetToken)
            
            return Success(null)
            
        } catch (Exception e) {
            logger.error("Forgot password error", e)
            return Error("Failed to initiate password reset")
        }
    }
    
    @Transactional
    public resetPassword(token: String, newPassword: String): Result<Void> {
        try {
            let userId = validateResetToken(token)
            if (userId == null) {
                return Error("Invalid or expired reset token")
            }
            
            let userOpt = userRepository.findById(userId)
            if (!userOpt.isPresent()) {
                return Error("User not found")
            }
            
            let user = userOpt.get()
            
            // Update password
            user.setPassword(passwordEncoder.encode(newPassword))
            userRepository.save(user)
            
            // Remove reset token
            removeResetToken(userId)
            
            // Invalidate all refresh tokens
            removeRefreshToken(userId)
            
            return Success(null)
            
        } catch (Exception e) {
            logger.error("Reset password error", e)
            return Error("Password reset failed")
        }
    }
    
    private findUserByEmailOrUsername(emailOrUsername: String): Optional<User> {
        // Try email first
        let userOpt = userRepository.findByEmail(emailOrUsername)
        if (userOpt.isPresent()) {
            return userOpt
        }
        
        // Try username
        return userRepository.findByUsername(emailOrUsername)
    }
    
    private generateRefreshToken(user: User): String {
        return UUID.randomUUID().toString()
    }
    
    private generateResetToken(): String {
        return UUID.randomUUID().toString()
    }
    
    private storeRefreshToken(userId: Long, refreshToken: String): void {
        let key = REFRESH_TOKEN_PREFIX + userId
        redisTemplate.setex(key, 7 * 24 * 3600, refreshToken) // 7 days
    }
    
    private validateRefreshToken(refreshToken: String): Long? {
        // Find user ID by refresh token (this is inefficient, consider using a different strategy)
        let keys = redisTemplate.keys(REFRESH_TOKEN_PREFIX + "*")
        for (key in keys) {
            let storedToken = redisTemplate.get(key)
            if (storedToken == refreshToken) {
                return key.substring(REFRESH_TOKEN_PREFIX.length()).toLong()
            }
        }
        return null
    }
    
    private removeRefreshToken(userId: Long): void {
        let key = REFRESH_TOKEN_PREFIX + userId
        redisTemplate.delete(key)
    }
    
    private storeResetToken(userId: Long, resetToken: String): void {
        let key = RESET_TOKEN_PREFIX + userId
        redisTemplate.setex(key, 3600, resetToken) // 1 hour
    }
    
    private validateResetToken(resetToken: String): Long? {
        let keys = redisTemplate.keys(RESET_TOKEN_PREFIX + "*")
        for (key in keys) {
            let storedToken = redisTemplate.get(key)
            if (storedToken == resetToken) {
                return key.substring(RESET_TOKEN_PREFIX.length()).toLong()
            }
        }
        return null
    }
    
    private removeResetToken(userId: Long): void {
        let key = RESET_TOKEN_PREFIX + userId
        redisTemplate.delete(key)
    }
    
    private blacklistToken(token: String): void {
        let key = BLACKLISTED_TOKEN_PREFIX + token
        let expirationTime = jwtUtil.extractExpiration(token)
        let ttl = (expirationTime.getTime() - System.currentTimeMillis()) / 1000
        if (ttl > 0) {
            redisTemplate.setex(key, ttl.toInt(), "blacklisted")
        }
    }
    
    public isTokenBlacklisted(token: String): Boolean {
        let key = BLACKLISTED_TOKEN_PREFIX + token
        return redisTemplate.exists(key)
    }
}
