package kr.co.accura.wbx.spring.auth;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.servlet.http.HttpServletRequest;
import kr.co.accura.wbx.spring.common.BusinessException;
import kr.co.accura.wbx.spring.config.WbxSpringProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("${wbx.spring.api-prefix:/api}/auth")
@RequiredArgsConstructor
public class AuthController {

    private final WbxUserRepository userRepository;
    private final JwtProvider jwtProvider;
    private final PasswordEncoder passwordEncoder;
    private final WbxSpringProperties props;
    private final RefreshTokenService refreshTokenService;
    private final LoginHistoryRepository loginHistoryRepository;
    private final PasswordPolicy passwordPolicy;

    /** MFA 서비스 — wbx.spring.mfa.enabled=true 일 때만 주입 */
    @org.springframework.beans.factory.annotation.Autowired(required = false)
    private MfaService mfaService;


    /**
     * 로그인 — WBX FastAPI 호환 JWT 발급
     */
    @PostMapping("/login")
    public Map<String, Object> login(@Valid @RequestBody LoginRequest req, HttpServletRequest httpReq) {
        WbxUser user = userRepository.findByEmail(req.email())
                .orElseThrow(() -> new BusinessException("Invalid email or password"));

        // 계정 잠금 체크
        if (user.isLocked()) {
            throw new BusinessException("Account locked. Try again later.", "ACCOUNT_LOCKED");
        }

        // 비밀번호 검증
        if (!passwordEncoder.matches(req.password(), user.getHashedPassword())) {
            user.recordLoginFailure(
                    props.getPassword().getMaxFailedAttempts(),
                    15 // lockout minutes
            );
            userRepository.save(user);
            throw new BusinessException("Invalid email or password");
        }

        if (!user.isActive()) {
            throw new BusinessException("User account is disabled");
        }

        // MFA 체크 — MFA 활성 사용자는 2단계 인증 필요
        if (mfaService != null && mfaService.isMfaRequired(user)) {
            Map<String, Object> mfaResponse = new HashMap<>();
            mfaResponse.put("mfa_required", true);
            mfaResponse.put("user_id", user.getId());
            mfaResponse.put("mfa_enabled", user.isMfaEnabled());
            return mfaResponse;
        }

        return completeLogin(user, httpReq, "PASSWORD");
    }

    /**
     * MFA 로그인 2단계 — TOTP 코드 검증 후 JWT 발급
     */
    @PostMapping("/mfa/verify")
    public Map<String, Object> mfaVerify(@Valid @RequestBody MfaVerifyRequest req, HttpServletRequest httpReq) {
        if (mfaService == null) {
            throw new BusinessException("MFA is not enabled");
        }
        WbxUser user = userRepository.findById(req.userId())
                .orElseThrow(() -> new BusinessException("User not found"));

        boolean valid = mfaService.verifyLogin(user.getId(), req.code());
        if (!valid) {
            throw new BusinessException("Invalid MFA code", "MFA_INVALID_CODE");
        }
        return completeLogin(user, httpReq, "MFA_TOTP");
    }

    /**
     * 로그인 완료 — JWT + Refresh Token 발급
     */
    private Map<String, Object> completeLogin(WbxUser user, HttpServletRequest httpReq, String authMethod) {
        user.recordLoginSuccess();
        userRepository.save(user);

        String token = jwtProvider.generateToken(user.toUserDetails());
        String ip = httpReq.getRemoteAddr();
        String ua = httpReq.getHeader("User-Agent");

        String refreshToken = refreshTokenService.create(user.getId(), ua, ip);

        loginHistoryRepository.save(WbxLoginHistory.builder()
                .userId(user.getId()).email(user.getEmail())
                .action("LOGIN_SUCCESS").authMethod(authMethod)
                .ipAddress(ip).userAgent(ua != null ? ua.substring(0, Math.min(ua.length(), 500)) : null)
                .build());

        Map<String, Object> result = new HashMap<>();
        result.put("access_token", token);
        result.put("refresh_token", refreshToken);
        result.put("token_type", "bearer");
        result.put("user", Map.of(
                "id", user.getId(),
                "email", user.getEmail(),
                "username", user.getUsername(),
                "full_name", user.getFullName() != null ? user.getFullName() : "",
                "is_admin", user.isAdmin(),
                "department_id", user.getDepartmentId() != null ? user.getDepartmentId() : 0
        ));
        result.put("must_change_password", user.isMustChangePassword());
        return result;
    }

    /**
     * 회원가입 (SA 전용 또는 초기 설정용)
     */
    @PostMapping("/register")
    public Map<String, Object> register(@Valid @RequestBody RegisterRequest req) {
        if (userRepository.existsByEmail(req.email())) {
            throw new BusinessException("Email already exists");
        }
        if (userRepository.existsByUsername(req.username())) {
            throw new BusinessException("Username already exists");
        }

        passwordPolicy.validate(req.password());

        WbxUser user = WbxUser.builder()
                .email(req.email())
                .username(req.username())
                .hashedPassword(passwordEncoder.encode(req.password()))
                .fullName(req.fullName())
                .isActive(true)
                .isAdmin(req.isAdmin() != null && req.isAdmin())
                .build();

        userRepository.save(user);
        log.info("User registered: {}", user.getEmail());

        return Map.of("detail", "User registered successfully", "id", user.getId());
    }

    /**
     * 내 정보 조회
     */
    @GetMapping("/me")
    public Map<String, Object> me(@AuthenticationPrincipal WbxUserDetails user) {
        return Map.of(
                "id", user.getId(),
                "email", user.getEmail(),
                "username", user.getUsername(),
                "full_name", user.getFullName(),
                "is_admin", user.isAdmin(),
                "department_id", user.getDepartmentId() != null ? user.getDepartmentId() : 0,
                "roles", user.getRoles()
        );
    }

    /**
     * 비밀번호 변경
     */
    @PutMapping("/password/change")
    public Map<String, String> changePassword(@AuthenticationPrincipal WbxUserDetails userDetails,
                                               @Valid @RequestBody PasswordChangeRequest req) {
        WbxUser user = userRepository.findById(userDetails.getId())
                .orElseThrow(() -> new BusinessException("User not found"));

        if (!passwordEncoder.matches(req.currentPassword(), user.getHashedPassword())) {
            throw new BusinessException("Current password is incorrect");
        }

        passwordPolicy.validate(req.newPassword());

        user.setHashedPassword(passwordEncoder.encode(req.newPassword()));
        user.setMustChangePassword(false);
        userRepository.save(user);

        return Map.of("detail", "Password changed successfully");
    }

    /**
     * Access Token 갱신
     */
    @PostMapping("/refresh")
    public Map<String, Object> refresh(@RequestBody Map<String, String> req) {
        String refreshToken = req.get("refresh_token");
        if (refreshToken == null || refreshToken.isBlank()) {
            throw new BusinessException("refresh_token is required");
        }
        String newToken = refreshTokenService.refresh(refreshToken);
        return Map.of("access_token", newToken, "token_type", "bearer");
    }

    /**
     * 로그아웃
     */
    @PostMapping("/logout")
    public Map<String, String> logout(@AuthenticationPrincipal WbxUserDetails user,
                                       @RequestBody(required = false) Map<String, String> req) {
        if (req != null && req.get("refresh_token") != null) {
            refreshTokenService.revoke(req.get("refresh_token"));
        }
        if (user != null) {
            loginHistoryRepository.save(WbxLoginHistory.builder()
                    .userId(user.getId()).email(user.getEmail())
                    .action("LOGOUT").authMethod("TOKEN")
                    .build());
        }
        return Map.of("detail", "Logged out successfully");
    }

    // ===== Request DTOs =====

    public record LoginRequest(
            @NotBlank @Email String email,
            @NotBlank String password
    ) {}

    public record RegisterRequest(
            @NotBlank @Email String email,
            @NotBlank String username,
            @NotBlank String password,
            String fullName,
            Boolean isAdmin
    ) {}

    public record PasswordChangeRequest(
            @NotBlank String currentPassword,
            @NotBlank String newPassword
    ) {}

    public record MfaVerifyRequest(
            Long userId,
            @NotBlank String code
    ) {}
}
