package kr.itn.itnhub.config;

import kr.itn.itnhub.contact.ContactNotFoundException;
import kr.itn.itnhub.contact.InvalidContactException;
import kr.itn.itnhub.feed.ChannelNotReadyException;
import kr.itn.itnhub.feed.InvalidMessageException;
import kr.itn.itnhub.org.OrgNotFoundException;
import kr.itn.itnhub.seed.SeedParseException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Map;
import java.util.stream.Collectors;

/**
 * 운영자의 흔한 실수를 서버 장애(500)가 아니라 4xx로 돌려준다.
 *
 * <p>{@link OrgNotFoundException}은 {@code OrganizationService.updateContact}와
 * {@code ChannelProvisionService.provision}이 존재하지 않는 기관 id에 대해 던진다 -
 * URL에 오타가 있는 것뿐이므로 404가 맞다.</p>
 *
 * <p>{@link MethodArgumentNotValidException}은 {@code @Valid} 대상 요청 바디의 필드가
 * 비어있거나 형식이 틀렸을 때 Spring이 던진다 - 원본 필드명을 그대로 보여주는 대신
 * 화면에 쓰는 한글 라벨로 바꿔서 알려준다.</p>
 *
 * <p>{@link SeedParseException}은 비어 있거나, 형식이 틀렸거나, 시트가 잘못된 업로드에
 * 대해 던진다 - 흔한 사용자 실수이므로 400과 함께 무엇이 잘못됐는지 알려줘야 한다.</p>
 *
 * <p>예외 메시지는 각 예외가 만든 그대로 내려준다(새로 지어내지 않는다) - 스택트레이스,
 * SQL, 파일 경로는 어차피 이 메시지들에 담기지 않으므로 그대로 노출해도 안전하다.
 * {@code server.error.include-message=NEVER} 기본값은 건드리지 않는다 - 여기서 직접
 * {@link ApiError} 본문을 만들어 반환하므로 그 설정과는 무관하게 동작한다.</p>
 *
 * <p>{@link ChannelNotReadyException}은 {@code ChannelFeedService}가 존재하는 기관의
 * 문정원/법률검토 채널이 아직 생성되지 않았거나 channel 파라미터가 잘못됐을 때 던진다 -
 * 기관은 있고(404 아님) 요청 자체도 잘못되지 않았으니(400 아님) "아직 처리할 수 없는
 * 상태"라는 의미로 409를 쓴다.</p>
 *
 * <p>{@link InvalidMessageException}은 채팅 탭에서 메시지 본문과 첨부파일이 둘 다
 * 없는 채로 전송을 시도할 때 던진다 - 흔한 사용자 실수이므로 400을 쓴다.</p>
 *
 * <p>{@link ContactNotFoundException}은 존재하지 않는 담당자 id로 조회/수정/삭제를
 * 시도했을 때 던진다 - URL의 id가 단순히 틀린 것뿐이므로 404가 맞다.</p>
 *
 * <p>{@link InvalidContactException}은 담당자 구분(category)이 APPLICANT/MJ/LAWYER
 * 중 하나가 아니거나, 기관에 배정하려는 담당자의 구분이 그 역할과 맞지 않을 때
 * 던진다 - 흔한 사용자 실수이므로 400을 쓴다.</p>
 *
 * <p><b>여기에 {@code Exception.class} catch-all을 추가하지 말 것.</b> 예상하지 못한
 * 예외까지 4xx로 감싸버리면 진짜 버그가 조용히 묻힌다. 예상 밖 예외는 기본 500 처리
 * 그대로 두는 것이 의도다.</p>
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /** 검증 오류 메시지에 쓸 필드명 → 한글 라벨. 목록에 없는 필드는 원본 이름 그대로 보여준다. */
    private static final Map<String, String> FIELD_LABELS = Map.of(
            "deptName", "부서명",
            "contactName", "담당자",
            "body", "메모 내용",
            "category", "구분",
            "name", "성명");

    @ExceptionHandler(OrgNotFoundException.class)
    public ResponseEntity<ApiError> handleNotFound(OrgNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ApiError(e.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException e) {
        String fields = e.getBindingResult().getFieldErrors().stream()
                .map(fe -> FIELD_LABELS.getOrDefault(fe.getField(), fe.getField()))
                .distinct()
                .collect(Collectors.joining(", "));
        return ResponseEntity.badRequest().body(new ApiError(fields + " 값을 확인해 주세요."));
    }

    @ExceptionHandler(SeedParseException.class)
    public ResponseEntity<ApiError> handleSeedParse(SeedParseException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ApiError(e.getMessage()));
    }

    @ExceptionHandler(ChannelNotReadyException.class)
    public ResponseEntity<ApiError> handleChannelNotReady(ChannelNotReadyException e) {
        return ResponseEntity.status(HttpStatus.CONFLICT).body(new ApiError(e.getMessage()));
    }

    @ExceptionHandler(InvalidMessageException.class)
    public ResponseEntity<ApiError> handleInvalidMessage(InvalidMessageException e) {
        return ResponseEntity.badRequest().body(new ApiError(e.getMessage()));
    }

    @ExceptionHandler(ContactNotFoundException.class)
    public ResponseEntity<ApiError> handleContactNotFound(ContactNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ApiError(e.getMessage()));
    }

    @ExceptionHandler(InvalidContactException.class)
    public ResponseEntity<ApiError> handleInvalidContact(InvalidContactException e) {
        return ResponseEntity.badRequest().body(new ApiError(e.getMessage()));
    }
}
