package dev.awesomedev.mirim.service;

import dev.awesomedev.mirim.domain.StudentProfile;
import dev.awesomedev.mirim.domain.User;
import dev.awesomedev.mirim.repository.StudentProfileRepository;
import dev.awesomedev.mirim.repository.UserRepository;
import dev.awesomedev.mirim.web.dto.ProfileResponse;
import dev.awesomedev.mirim.web.dto.ProfileUpdateRequest;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * 이 파일이 하는 일:
 * 수습생 인적사항 조회와, 경리 담당자를 위한 CSV 명단 만들기.
 *
 * 학습 포인트: "화면에 표로 보여주기"와 "파일로 내려받기"는 같은 데이터의 다른 출구다.
 * 데이터를 꺼내는 일(리포지토리)은 공유하고, 표현(JSON이냐 CSV냐)만 다르게 한다.
 */
@Service
public class StudentProfileService {

    private final StudentProfileRepository studentProfileRepository;
    private final UserRepository userRepository;
    private final TelegramNotifier telegramNotifier;

    public StudentProfileService(StudentProfileRepository studentProfileRepository,
                                 UserRepository userRepository,
                                 TelegramNotifier telegramNotifier) {
        this.studentProfileRepository = studentProfileRepository;
        this.userRepository = userRepository;
        this.telegramNotifier = telegramNotifier;
    }

    /**
     * 내 인적사항 조회.
     * 학습 포인트: DTO 변환(ProfileResponse.from)이 p.getUser()를 만지는데 user는 LAZY다.
     * open-in-view: false에서는 트랜잭션 밖에서 만지면 예외가 나므로,
     * 엔티티가 아니라 "변환이 끝난 DTO"를 트랜잭션 안에서 만들어 반환한다.
     * (멘토 명단의 JOIN FETCH와 같은 함정을 다른 방법으로 푼 것 — 방법은 여러 가지다.)
     */
    @Transactional(readOnly = true)
    public ProfileResponse myProfile(User loginUser) {
        StudentProfile profile = studentProfileRepository.findByUser(loginUser)
                .orElseThrow(() -> new ResponseStatusException(
                        HttpStatus.NOT_FOUND, "인적사항이 없습니다. (멘토 계정에는 인적사항이 없어요.)"));
        return ProfileResponse.from(profile);
    }

    /**
     * 내 인적사항 수정.
     * 학습 포인트: 비밀번호 변경 버그의 교훈 그대로 — 수정할 엔티티는 반드시
     * 트랜잭션 "안에서" 다시 조회한다(managed). 세션에서 온 loginUser는 detached라
     * 바꿔도 저장되지 않는다.
     */
    @Transactional
    public ProfileResponse updateMyProfile(User loginUser, ProfileUpdateRequest req) {
        User user = userRepository.findById(loginUser.getId())
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다."));
        StudentProfile profile = studentProfileRepository.findByUser(user)
                .orElseThrow(() -> new ResponseStatusException(
                        HttpStatus.NOT_FOUND, "인적사항이 없습니다. (멘토 계정에는 인적사항이 없어요.)"));

        // 이메일 아이디를 "다른 값으로" 바꿀 때만 중복 검사 — 내 것 그대로면 통과해야 한다.
        if (!profile.getCompanyEmailLocal().equals(req.companyEmailLocal())
                && studentProfileRepository.existsByCompanyEmailLocal(req.companyEmailLocal())) {
            throw new ResponseStatusException(HttpStatus.CONFLICT, "이미 사용 중인 회사 이메일 아이디입니다.");
        }

        user.rename(req.name());
        profile.update(req.companyEmailLocal(), req.birthDate(), req.phone(), req.address(),
                req.school(), req.major(),
                req.emergencyName(), req.emergencyRelation(), req.emergencyPhone());

        // 멘토에게 알림 — 경리 서류(CSV)를 이미 뽑았다면 다시 뽑아야 하니까.
        telegramNotifier.send("✏️ 내 정보 수정\n" + user.getName() + " (" + user.getUsername() + ")");

        return ProfileResponse.from(profile);
    }

    @Transactional(readOnly = true)
    public List<StudentProfile> allProfiles() {
        // JOIN FETCH로 User까지 한 번에 — 트랜잭션 밖에서 getUser()를 써도 안전하다.
        return studentProfileRepository.findAllWithUser();
    }

    /**
     * 전체 수습생 인적사항을 CSV 문자열로 만든다. 경리 담당자가 엑셀로 열어 쓴다.
     *
     * 학습 포인트 ① — CSV 이스케이프.
     *   주소에 쉼표(,)가 들어가면 열이 밀린다. 그래서 각 값을 큰따옴표로 감싸고,
     *   값 안의 큰따옴표는 두 개("")로 바꾼다. 이게 CSV의 표준 규칙이다.
     *   "간단해 보이는 형식일수록 예외 처리를 빠뜨리기 쉽다."
     *
     * 학습 포인트 ② — 엑셀 한글 깨짐 방지.
     *   맨 앞에 BOM(﻿)을 붙인다. 이게 없으면 엑셀이 UTF-8을 못 알아보고 한글이 깨진다.
     *   (컨트롤러에서 UTF-8로 인코딩해 내려보낸다.)
     */
    @Transactional(readOnly = true)
    public String exportCsv() {
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        StringBuilder sb = new StringBuilder("﻿"); // BOM

        // 헤더
        sb.append("이름,회사이메일,트랙,생년월일,연락처,주소,학교,전공,비상연락-이름,비상연락-관계,비상연락-전화,개인정보동의일,가입일\n");

        for (StudentProfile p : allProfiles()) {
            sb.append(csv(p.getUser().getName())).append(',')
              .append(csv(p.fullEmail())).append(',')
              .append(csv("DESIGN".equals(p.getUser().getTrack()) ? "디자인" : "개발")).append(',')
              .append(csv(p.getBirthDate().toString())).append(',')
              .append(csv(p.getPhone())).append(',')
              .append(csv(p.getAddress())).append(',')
              .append(csv(p.getSchool())).append(',')
              .append(csv(p.getMajor())).append(',')
              .append(csv(p.getEmergencyName())).append(',')
              .append(csv(p.getEmergencyRelation())).append(',')
              .append(csv(p.getEmergencyPhone())).append(',')
              // 개인정보 동의일 — 감사·증빙 대비. (동의 없이 가입된 행은 존재할 수 없다)
              .append(csv(df.format(p.getPrivacyConsentAt().atZone(
                      java.time.ZoneId.of("Asia/Seoul"))))).append(',')
              .append(csv(df.format(p.getUser().getCreatedAt().atZone(
                      java.time.ZoneId.of("Asia/Seoul")))))
              .append('\n');
        }
        return sb.toString();
    }

    /** CSV 한 칸 이스케이프: 큰따옴표로 감싸고, 안의 큰따옴표는 두 개로. */
    private String csv(String value) {
        String v = value == null ? "" : value;
        return "\"" + v.replace("\"", "\"\"") + "\"";
    }
}
