package com.kamco.cd.training.members;

import com.kamco.cd.training.auth.CustomUserDetails;
import com.kamco.cd.training.auth.JwtTokenProvider;
import com.kamco.cd.training.common.enums.StatusType;
import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.members.dto.MembersDto;
import com.kamco.cd.training.members.dto.SignInRequest;
import com.kamco.cd.training.members.dto.TokenResponse;
import com.kamco.cd.training.members.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
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.HttpServletResponse;
import java.nio.file.AccessDeniedException;
import java.time.Duration;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API")
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {

  private final AuthenticationManager authenticationManager;
  private final JwtTokenProvider jwtTokenProvider;
  private final AuthService authService;

  @Value("${token.refresh-cookie-name}")
  private String refreshCookieName;

  @Value("${token.refresh-cookie-secure:true}")
  private boolean refreshCookieSecure;

  @PostMapping("/signin")
  @Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.")
  @ApiResponses({
    @ApiResponse(
        responseCode = "200",
        description = "로그인 성공",
        content = @Content(schema = @Schema(implementation = TokenResponse.class))),
    @ApiResponse(
        responseCode = "401",
        description = "로그인 실패 (아이디/비밀번호 오류, 계정잠금 등)",
        content =
            @Content(
                schema = @Schema(implementation = ErrorResponse.class),
                examples = {
                  @ExampleObject(
                      name = "사번 입력 오류",
                      description = "존재하지 않는 아이디",
                      value =
                          """
    {
      "code": "LOGIN_ID_NOT_FOUND",
      "message": "사번을 잘못 입력하셨습니다."
    }
    """),
                  @ExampleObject(
                      name = "비밀번호 입력 오류 (4회 이하)",
                      description = "아이디는 정상, 비밀번호를 여러 번 틀린 경우",
                      value =
                          """
    {
      "code": "LOGIN_PASSWORD_MISMATCH",
      "message": "비밀번호를 잘못 입력하셨습니다."
    }
    """),
                  @ExampleObject(
                      name = "비밀번호 오류 횟수 초과",
                      description = "비밀번호 5회 이상 오류로 계정 잠김",
                      value =
                          """
    {
      "code": "LOGIN_PASSWORD_EXCEEDED",
      "message": "비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다. 로그인 오류에 대해 관리자에게 문의하시기 바랍니다."
    }
    """),
                  @ExampleObject(
                      name = "사용 중지 된 계정의 로그인 시도",
                      description = "사용 중지 된 계정의 로그인 시도",
                      value =
                          """
    {
      "code": "INACTIVE_ID",
      "message": "사용할 수 없는 계정입니다."
    }
    """)
                }))
  })
  public ApiResponseDto<TokenResponse> signin(
      @io.swagger.v3.oas.annotations.parameters.RequestBody(
              description = "로그인 요청 정보",
              required = true)
          @RequestBody
          SignInRequest request,
      HttpServletResponse response) {

    // 사용자 상태 조회
    String status = authService.getUserStatus(request);

    if (StatusType.INACTIVE.getId().equals(status)) {
      throw new CustomApiException("INACTIVE_ID", HttpStatus.UNAUTHORIZED);
    }

    Authentication authentication = null;
    MembersDto.Member member = new MembersDto.Member();

    authentication =
        authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));

    String username = authentication.getName(); // UserDetailsService 에서 사용한 username

    String accessToken = jwtTokenProvider.createAccessToken(username);
    String refreshToken = jwtTokenProvider.createRefreshToken(username);

    // 토큰 저장
    authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());

    // HttpOnly + Secure 쿠키에 RefreshToken 저장
    ResponseCookie cookie =
        ResponseCookie.from(refreshCookieName, refreshToken)
            .httpOnly(true)
            .secure(refreshCookieSecure)
            .path("/")
            .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
            .sameSite("Strict")
            .build();

    response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

    CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal();
    member.setId(user.getMember().getId());
    member.setName(user.getMember().getName());
    member.setEmployeeNo(user.getMember().getEmployeeNo());

    // PENDING 비활성 상태(새로운 패스워드 입력 해야함)
    if (StatusType.PENDING.getId().equals(status)) {
      member.setEmployeeNo(request.getUsername());
      return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member));
    }

    // 인증 성공 로그인 시간 저장
    authService.saveLogin(UUID.fromString(username));

    return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member));
  }

  @PostMapping("/refresh")
  @Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.")
  @ApiResponses({
    @ApiResponse(
        responseCode = "200",
        description = "재발급 성공",
        content = @Content(schema = @Schema(implementation = TokenResponse.class))),
    @ApiResponse(
        responseCode = "403",
        description = "만료되었거나 유효하지 않은 리프레시 토큰",
        content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
  })
  public ResponseEntity<TokenResponse> refresh(String refreshToken, HttpServletResponse response)
      throws AccessDeniedException {
    if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
      throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
    }
    String username = jwtTokenProvider.getSubject(refreshToken);

    // 저장된 RefreshToken과 일치하는지 확인
    authService.validateRefreshToken(username, refreshToken);

    // 새 토큰 발급
    String newAccessToken = jwtTokenProvider.createAccessToken(username);
    String newRefreshToken = jwtTokenProvider.createRefreshToken(username);

    // 토큰 저장
    authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());

    // 쿠키 갱신
    ResponseCookie cookie =
        ResponseCookie.from(refreshCookieName, newRefreshToken)
            .httpOnly(true)
            .secure(refreshCookieSecure)
            .path("/")
            .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
            .sameSite("Strict")
            .build();
    response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

    MembersDto.Member member = new MembersDto.Member();
    return ResponseEntity.ok(new TokenResponse("ACTIVE", newAccessToken, newRefreshToken, member));
  }

  @PostMapping("/logout")
  @Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.")
  @ApiResponses({
    @ApiResponse(
        responseCode = "200",
        description = "로그아웃 성공",
        content = @Content(schema = @Schema(implementation = Void.class)))
  })
  public ApiResponseDto<ResponseEntity<Object>> logout(
      Authentication authentication, HttpServletResponse response) {
    if (authentication != null) {
      String username = authentication.getName();
      authService.logout(username);
    }

    // 쿠키 삭제 (Max-Age=0)
    ResponseCookie cookie =
        ResponseCookie.from(refreshCookieName, "")
            .httpOnly(true)
            .secure(refreshCookieSecure)
            .path("/")
            .maxAge(0)
            .sameSite("Strict")
            .build();
    response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

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