package com.zioinfo.kintex.auth;

import com.zioinfo.kintex.auth.dto.ChangePasswordRequest;
import com.zioinfo.kintex.auth.dto.MeResponse;
import com.zioinfo.kintex.auth.dto.NotificationPrefsDto;
import com.zioinfo.kintex.auth.dto.ProfileUpdateRequest;
import com.zioinfo.kintex.auth.mapper.NotificationPrefMapper;
import com.zioinfo.kintex.auth.mapper.UserMapper;
import com.zioinfo.kintex.auth.profile.ProfilePhotoService;
import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;

/**
 * 마이페이지(SCR-48) 계정 설정 서비스 — 프로필 수정·비밀번호 변경·알림 설정(GAP G-08).
 *
 * <p>기존 {@code AuthServiceImpl}(로그인·me)·2FA 로직은 <b>교체하지 않고</b> 순증한다.
 * 모든 변경은 <b>본인({@code principal.userId()})</b>에 대해서만 수행하며 대상 userId 를 요청 본문으로 받지 않는다.
 * 보안 불변(계약 §0-3): password_hash 는 검증 용도로만 조회하고 응답/로그에 노출하지 않으며,
 * 프로필 수정은 화이트리스트(display_name·phone)만 허용한다(email·role·dept 변경 불가).
 */
@Service
public class AccountSettingsService {

    private static final Logger log = LoggerFactory.getLogger(AccountSettingsService.class);

    private final UserMapper userMapper;
    private final NotificationPrefMapper notifPrefMapper;
    private final PasswordEncoder passwordEncoder;

    public AccountSettingsService(UserMapper userMapper, NotificationPrefMapper notifPrefMapper,
                                  PasswordEncoder passwordEncoder) {
        this.userMapper = userMapper;
        this.notifPrefMapper = notifPrefMapper;
        this.passwordEncoder = passwordEncoder;
    }

    /** 프로필 수정 — 화이트리스트(displayName·phone)만. 갱신 후 최신 {@link MeResponse} 반환(웹 부트스트랩 재사용). */
    @Transactional
    public MeResponse updateProfile(KintexPrincipal principal, ProfileUpdateRequest req) {
        String displayName = req.displayName().trim();
        if (displayName.isEmpty()) {
            throw new ApiException(ErrorCode.VALIDATION, "이름을 입력해 주세요.");
        }
        String phone = blankToNull(req.phone());
        int rows = userMapper.updateProfile(principal.userId(), displayName, phone);
        if (rows == 0) {
            throw new ApiException(ErrorCode.NOT_FOUND, "사용자를 찾을 수 없습니다.");
        }
        return buildMe(principal);
    }

    /** 비밀번호 변경 — 현재 비밀번호 검증 + 정책(신규 != 현재) 후 해시 갱신. */
    @Transactional
    public void changePassword(KintexPrincipal principal, ChangePasswordRequest req) {
        String currentHash = userMapper.findPasswordHash(principal.userId());
        if (currentHash == null || !passwordEncoder.matches(req.currentPassword(), currentHash)) {
            // 현재 비밀번호 불일치 — 일반화 메시지(구체 사유 미노출).
            throw new ApiException(ErrorCode.UNAUTHORIZED, "현재 비밀번호가 올바르지 않습니다.");
        }
        if (passwordEncoder.matches(req.newPassword(), currentHash)) {
            throw new ApiException(ErrorCode.VALIDATION, "새 비밀번호는 현재 비밀번호와 달라야 합니다.");
        }
        userMapper.updatePassword(principal.userId(), passwordEncoder.encode(req.newPassword()));
        // 원문·해시 미기록(계약 §0-3).
        log.info("비밀번호 변경 완료(userId={})", principal.userId());
    }

    /** 알림 설정 조회 — 미설정 사용자는 서버 기본값(전체 수신, 이메일 off). */
    public NotificationPrefsDto getNotificationPrefs(KintexPrincipal principal) {
        Map<String, Object> row = notifPrefMapper.find(principal.userId());
        if (row == null) {
            return NotificationPrefsDto.defaults();
        }
        return new NotificationPrefsDto(
                bool(row.get("notifyDeadline"), true),
                bool(row.get("notifyApproval"), true),
                bool(row.get("notifyPayment"), true),
                bool(row.get("emailEnabled"), false));
    }

    /** 알림 설정 저장(upsert, 멱등) — 저장값을 그대로 반환. */
    @Transactional
    public NotificationPrefsDto saveNotificationPrefs(KintexPrincipal principal, NotificationPrefsDto req) {
        notifPrefMapper.upsert(principal.userId(),
                req.notifyDeadline(), req.notifyApproval(), req.notifyPayment(), req.emailEnabled());
        return req;
    }

    /** 갱신된 프로필로 {@link MeResponse} 재조립 — DB 기준 displayName·phone(토큰 stale 회피) + principal 역할. */
    private MeResponse buildMe(KintexPrincipal principal) {
        Map<String, Object> p = userMapper.findProfile(principal.userId());
        String displayName = p != null && p.get("displayName") != null
                ? String.valueOf(p.get("displayName")) : principal.displayName();
        String email = p == null ? null : str(p.get("email"));
        String phone = p == null ? null : str(p.get("phone"));
        String deptId = p == null ? null : str(p.get("deptId"));
        boolean hasPhoto = p != null && Boolean.TRUE.equals(bool(p.get("hasPhoto"), false));
        String photoUrl = hasPhoto ? ProfilePhotoService.serveUrl(principal.userId()) : null;
        return new MeResponse(
                principal.userId(), displayName, principal.eventRoles(),
                principal.hallManager(), principal.roleCode(), principal.tenantId(),
                email, phone, deptId, photoUrl);
    }

    private static String str(Object o) {
        return o == null ? null : String.valueOf(o);
    }

    private static String blankToNull(String s) {
        return (s == null || s.isBlank()) ? null : s.trim();
    }

    private static boolean bool(Object o, boolean dflt) {
        if (o == null) return dflt;
        if (o instanceof Boolean b) return b;
        return Boolean.parseBoolean(String.valueOf(o));
    }
}
