package de.effigenix.infrastructure.usermanagement.web.controller;

import de.effigenix.application.usermanagement.AuthenticateUser;
import de.effigenix.application.usermanagement.SessionManager;
import de.effigenix.application.usermanagement.command.AuthenticateCommand;
import de.effigenix.application.usermanagement.dto.SessionToken;
import de.effigenix.domain.usermanagement.UserError;
import de.effigenix.infrastructure.security.LoginRateLimiter;
import de.effigenix.infrastructure.usermanagement.web.dto.LoginRequest;
import de.effigenix.infrastructure.usermanagement.web.dto.LoginResponse;
import de.effigenix.infrastructure.usermanagement.web.dto.RefreshTokenRequest;
import de.effigenix.shared.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

/**
 * REST Controller for Authentication endpoints.
 *
 * Security:
 * - Rate limiting on login endpoint (max 5 attempts per 15min per IP)
 * - All auth endpoints are PUBLIC (configured in SecurityConfig)
 */
@RestController
@RequestMapping("/api/auth")
@Tag(name = "Authentication", description = "Authentication and session management endpoints")
public class AuthController {

    private static final Logger logger = LoggerFactory.getLogger(AuthController.class);

    private final AuthenticateUser authenticateUser;
    private final SessionManager sessionManager;
    private final LoginRateLimiter rateLimiter;

    public AuthController(
            AuthenticateUser authenticateUser,
            SessionManager sessionManager,
            LoginRateLimiter rateLimiter
    ) {
        this.authenticateUser = authenticateUser;
        this.sessionManager = sessionManager;
        this.rateLimiter = rateLimiter;
    }

    @PostMapping("/login")
    @Operation(summary = "User login", description = "Authenticate user with username and password. Returns JWT tokens.")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Login successful",
                    content = @Content(schema = @Schema(implementation = LoginResponse.class))),
            @ApiResponse(responseCode = "401", description = "Invalid credentials, user locked, or user inactive"),
            @ApiResponse(responseCode = "429", description = "Too many login attempts")
    })
    public ResponseEntity<LoginResponse> login(
            @Valid @RequestBody LoginRequest request,
            HttpServletRequest httpRequest
    ) {
        String clientIp = getClientIp(httpRequest);

        // Rate limiting check
        if (rateLimiter.isRateLimited(clientIp)) {
            logger.warn("Rate limited login attempt from IP: {}", clientIp);
            throw new AuthenticationFailedException(new UserError.Unauthorized("Too many login attempts. Please try again later."));
        }

        logger.info("Login attempt for username: {}", request.username());

        AuthenticateCommand command = new AuthenticateCommand(request.username(), request.password());
        Result<UserError, SessionToken> result = authenticateUser.execute(command);

        if (result.isFailure()) {
            rateLimiter.recordFailedAttempt(clientIp);
            throw new AuthenticationFailedException(result.unsafeGetError());
        }

        rateLimiter.resetAttempts(clientIp);
        SessionToken token = result.unsafeGetValue();
        logger.info("Login successful for username: {}", request.username());

        return ResponseEntity.ok(LoginResponse.from(token));
    }

    @PostMapping("/logout")
    @Operation(summary = "User logout", description = "Invalidate current JWT token.")
    @ApiResponses({
            @ApiResponse(responseCode = "204", description = "Logout successful"),
            @ApiResponse(responseCode = "401", description = "Invalid or missing authentication token")
    })
    public ResponseEntity<Void> logout(HttpServletRequest request, Authentication authentication) {
        String token = extractTokenFromRequest(request);
        if (token != null) {
            sessionManager.invalidateSession(token);
            String username = authentication != null ? authentication.getName() : "unknown";
            logger.info("Logout successful for user: {}", username);
        }

        return ResponseEntity.noContent().build();
    }

    @PostMapping("/refresh")
    @Operation(summary = "Refresh access token", description = "Refresh an expired access token using a valid refresh token.")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Token refresh successful",
                    content = @Content(schema = @Schema(implementation = LoginResponse.class))),
            @ApiResponse(responseCode = "401", description = "Invalid or expired refresh token")
    })
    public ResponseEntity<LoginResponse> refresh(@Valid @RequestBody RefreshTokenRequest request) {
        logger.info("Token refresh attempt");

        try {
            SessionToken token = sessionManager.refreshSession(request.refreshToken());
            logger.info("Token refresh successful");
            return ResponseEntity.ok(LoginResponse.from(token));
        } catch (RuntimeException ex) {
            logger.warn("Token refresh failed");
            throw new AuthenticationFailedException(new UserError.InvalidCredentials());
        }
    }

    private String extractTokenFromRequest(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }

    private String getClientIp(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isBlank()) {
            return xForwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }

    public static class AuthenticationFailedException extends RuntimeException {
        private final UserError error;

        public AuthenticationFailedException(UserError error) {
            super(error.message());
            this.error = error;
        }

        public UserError getError() {
            return error;
        }
    }
}
