package com.ffii.fpsms.config.security.jwt.web;

import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import com.ffii.core.utils.AES;
import com.ffii.core.utils.JwtTokenUtil;
import com.ffii.fpsms.config.security.jwt.service.JwtUserDetailsService;
import com.ffii.fpsms.config.security.service.LoginLogService;
import com.ffii.fpsms.model.AbilityModel;
import com.ffii.fpsms.model.ExceptionResponse;
import com.ffii.fpsms.model.JwtRequest;
import com.ffii.fpsms.model.JwtResponse;
import com.ffii.fpsms.model.RefreshToken;
import com.ffii.fpsms.model.TokenRefreshRequest;
import com.ffii.fpsms.model.TokenRefreshResponse;
import com.ffii.fpsms.modules.user.entity.User;
import com.ffii.fpsms.modules.user.entity.UserRepository;
import com.ffii.fpsms.modules.user.service.UserAuthorityService;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;

@RestController
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class JwtAuthenticationController {

  @Autowired
  @Qualifier("AuthenticationManager")
  private AuthenticationManager authenticationManager;

  @Autowired
  @Qualifier("LdapAuthenticationManager")
  private AuthenticationManager ldapAuthenticationManager;

  @Autowired
  private JwtTokenUtil jwtTokenUtil;

  @Autowired
  private JwtUserDetailsService userDetailsService;

  @Autowired
  private UserRepository userRepository;

  @Autowired
  UserAuthorityService userAuthorityService;

  @Autowired
  LoginLogService loginLogService;

  @PostMapping("/login")
  public ResponseEntity<?> login(@RequestBody JwtRequest authenticationRequest, HttpServletRequest request) throws Exception {
    String username = authenticationRequest.getUsername();
    try {
      boolean success = authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
      loginLogService.createLoginLog(username, request.getRemoteAddr(), success);
    } catch (Exception e) {
      if (username != null) {
        loginLogService.createLoginLog(username, request.getRemoteAddr(), false);
      }
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
          .body(new ExceptionResponse("Unauthorized", ExceptionUtils.getStackTrace(e)));
    }
    return createAuthTokenResponse(authenticationRequest);
  }

  @PostMapping("/ldap-login")
  public ResponseEntity<?> ldapLogin(@RequestBody JwtRequest authenticationRequest, HttpServletRequest request) throws Exception {
    String username = authenticationRequest.getUsername();
    try {
      boolean success = ldapAuthenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
		  loginLogService.createLoginLog(username, request.getRemoteAddr(), success);
    } catch (Exception e) {
      loginLogService.createLoginLog(username, request.getRemoteAddr(), false);
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
          .body(new ExceptionResponse("Unauthorized", ExceptionUtils.getStackTrace(e)));
    }
    return createAuthTokenResponse(authenticationRequest);
  }

  private boolean authenticate(String username, String password) throws Exception {
    authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    return true;
  }

  private boolean ldapAuthenticate(String username, String password) throws Exception {
    ldapAuthenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    return true;
  }

  private ResponseEntity<?> createAuthTokenResponse(JwtRequest authenticationRequest) {
//    final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
    final User user = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
    if (user == null) {
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
          .body(new ExceptionResponse(authenticationRequest.getUsername() + " not yet register in the system.", null));
    }

    final String accessToken = jwtTokenUtil.generateToken(user);
    final String refreshToken = jwtTokenUtil.createRefreshToken(user.getUsername()).getToken();

//    User user = userRepository.findByName(authenticationRequest.getUsername()).get(0);

    List<String> abilities = new ArrayList<>();
    final Set<SimpleGrantedAuthority> userAuthority = userAuthorityService.getUserAuthority(user);
    if (!userAuthority.isEmpty()) {
      abilities = userAuthority.stream()
        .map(auth -> auth.getAuthority()).toList();
    }
    return ResponseEntity.ok(new JwtResponse(accessToken, refreshToken, null, user, abilities));
  }

  @PostMapping("/refresh-token")
  public ResponseEntity<TokenRefreshResponse> refreshtoken(@Valid @RequestBody TokenRefreshRequest request)
      throws Exception {
    String requestRefreshToken = request.getRefreshToken();

    requestRefreshToken = requestRefreshToken.replaceAll("\"", "");
    String[] decryptStringList = AES.decrypt(requestRefreshToken, JwtTokenUtil.AES_SECRET)
        .split(JwtTokenUtil.TOKEN_SEPARATOR);
    RefreshToken instance = new RefreshToken();
    String username = decryptStringList[0];
    instance.setExpiryDate(Instant.ofEpochMilli(Long.valueOf(decryptStringList[1])));
    instance.setToken(requestRefreshToken);
    instance.setUserName(decryptStringList[0]);

    if (!jwtTokenUtil.verifyExpiration(instance)) {
      throw new ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
          "Refresh token was expired. Please make a new signin request");
    }

    final UserDetails userDetails = userDetailsService.loadUserByUsername(username);

    String accessToken = jwtTokenUtil.generateToken(userDetails);
    String refreshToken = jwtTokenUtil.createRefreshToken(username).getToken();
    return ResponseEntity.ok(new TokenRefreshResponse(accessToken, refreshToken));
  }

}
