package com.devjob.service;

import com.devjob.domain.Application;
import com.devjob.domain.Job;
import com.devjob.domain.JobStatus;
import com.devjob.domain.Member;
import com.devjob.dto.ApplicationResponse;
import com.devjob.event.ApplicationSubmittedEvent;
import com.devjob.exception.BusinessException;
import com.devjob.exception.ErrorCode;
import com.devjob.repository.ApplicationRepository;
import com.devjob.repository.JobRepository;
import com.devjob.repository.MemberRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * EP24: 채용 공고 지원.
 * - 마감된 공고(JOB_CLOSED), 중복 지원(ALREADY_APPLIED)을 막는다.
 *
 * EP44: 이메일 발송을 직접 호출하지 않고 ApplicationSubmittedEvent를 발행한다.
 *       (이메일/알림 등 부가 기능과의 결합도를 낮추기 위함 — ApplicationEventListener 참고)
 */
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ApplicationService {

    private final ApplicationRepository applicationRepository;
    private final JobRepository jobRepository;
    private final MemberRepository memberRepository;
    private final ApplicationEventPublisher eventPublisher;

    @Transactional
    public ApplicationResponse apply(Long memberId, Long jobId) {
        Job job = jobRepository.findById(jobId)
                .orElseThrow(() -> new BusinessException(ErrorCode.JOB_NOT_FOUND));

        if (job.getStatus() == JobStatus.CLOSED) {
            throw new BusinessException(ErrorCode.JOB_CLOSED);
        }

        if (applicationRepository.existsByMemberIdAndJobId(memberId, jobId)) {
            throw new BusinessException(ErrorCode.ALREADY_APPLIED);
        }

        Member member = memberRepository.findById(memberId)
                .orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));

        Application application = Application.builder()
                .member(member)
                .job(job)
                .build();

        Application saved = applicationRepository.save(application);

        // EP44: 이벤트 발행은 반드시 @Transactional 메서드 "안"에서 호출해야
        // @TransactionalEventListener(AFTER_COMMIT)이 트랜잭션 커밋 시점에 정상 실행된다.
        eventPublisher.publishEvent(new ApplicationSubmittedEvent(saved));

        // saved.getJob()은 위에서 조회한 영속 엔티티이므로 company까지 이미 로딩되어 있지 않을 수 있음에 주의.
        // ApplicationResponse.from()에서 job.getCompany().getName()을 호출하므로,
        // job 조회 시 fetch join을 쓰지 않았다면 LAZY 프록시 초기화 시점에 추가 쿼리가 발생할 수 있다.
        return ApplicationResponse.from(saved);
    }

    public List<ApplicationResponse> findMyApplications(Long memberId) {
        return applicationRepository.findByMemberIdWithJob(memberId).stream()
                .map(ApplicationResponse::from)
                .toList();
    }
}
