package com.emonster.taroaichat.web.rest;

import com.emonster.taroaichat.security.jwt.TokenProvider;
import com.emonster.taroaichat.security.jwt.JWTToken;
import com.emonster.taroaichat.web.rest.vm.LoginVM;
import com.emonster.taroaichat.web.rest.vm.RefreshTokenVM;
import jakarta.validation.Valid;
import java.security.Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.web.bind.annotation.*;

/**
 * Controller to authenticate users.
 */
@RestController
@RequestMapping("/api")
public class AuthenticateController {

    private static final Logger LOG = LoggerFactory.getLogger(AuthenticateController.class);

    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final JwtDecoder refreshTokenDecoder;
    private final UserDetailsService userDetailsService;

    public AuthenticateController(
        TokenProvider tokenProvider,
        AuthenticationManagerBuilder authenticationManagerBuilder,
        @Qualifier("refreshTokenJwtDecoder") JwtDecoder refreshTokenDecoder,
        UserDetailsService userDetailsService
    ) {
        this.tokenProvider = tokenProvider;
        this.authenticationManagerBuilder = authenticationManagerBuilder;
        this.refreshTokenDecoder = refreshTokenDecoder;
        this.userDetailsService = userDetailsService;
    }

    @PostMapping("/authenticate")
    public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
            loginVM.getUsername(),
            loginVM.getPassword()
        );

        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        String accessToken = tokenProvider.createAccessToken(authentication, loginVM.isRememberMe());
        String refreshToken = tokenProvider.createRefreshToken(authentication);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setBearerAuth(accessToken);
        return new ResponseEntity<>(new JWTToken(accessToken, refreshToken), httpHeaders, HttpStatus.OK);
    }

    /**
     * {@code GET /authenticate} : check if the user is authenticated.
     *
     * @return the {@link ResponseEntity} with status {@code 204 (No Content)},
     * or with status {@code 401 (Unauthorized)} if not authenticated.
     */
    @GetMapping("/authenticate")
    public ResponseEntity<Void> isAuthenticated(Principal principal) {
        LOG.debug("REST request to check if the current user is authenticated");
        return ResponseEntity.status(principal == null ? HttpStatus.UNAUTHORIZED : HttpStatus.NO_CONTENT).build();
    }

    /**
     * {@code POST /authenticate/refresh} : refresh the JWT token using a refresh token.
     *
     * @param refreshTokenRequest the refresh token request containing the refresh token
     * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the new JWT token,
     * or with status {@code 401 (Unauthorized)} if the refresh token is invalid
     */
    @PostMapping("/authenticate/refresh")
    public ResponseEntity<JWTToken> refreshToken(@Valid @RequestBody RefreshTokenVM refreshTokenRequest) {
        try {
            // Decode and validate the refresh token
            Jwt jwt = refreshTokenDecoder.decode(refreshTokenRequest.getRefreshToken());

            // Check if it's a refresh token
            String tokenType = jwt.getClaimAsString("token_type");
            if (!"refresh".equals(tokenType)) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
            }

            // Get the username from the refresh token
            String username = jwt.getSubject();

            // Load user details
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            // Create a new authentication token
            UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

            // Generate new tokens
            String newAccessToken = tokenProvider.createAccessToken(authentication, false);
            String newRefreshToken = tokenProvider.createRefreshToken(authentication);

            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setBearerAuth(newAccessToken);

            return new ResponseEntity<>(new JWTToken(newAccessToken, newRefreshToken), httpHeaders, HttpStatus.OK);
        } catch (JwtException e) {
            LOG.debug("Invalid refresh token: {}", e.getMessage());
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
    }

}
