package com.devjob.controller;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.devjob.domain.Company;
import com.devjob.domain.Member;
import com.devjob.domain.Role;
import com.devjob.dto.JobCreateRequest;
import com.devjob.dto.JobUpdateRequest;
import com.devjob.repository.CompanyRepository;
import com.devjob.repository.MemberRepository;
import com.devjob.security.CustomUserDetails;
import tools.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.transaction.annotation.Transactional;

/**
 * EP18: 채용 공고 CRUD API 통합 테스트.
 * 공고 등록·수정·삭제는 인증이 필요하고, 수정·삭제는 작성자 본인(또는 ADMIN)만 가능하다.
 */
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class JobControllerTest {

    @Autowired
    private MockMvc mockMvc;


    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private CompanyRepository companyRepository;

    @Autowired
    private MemberRepository memberRepository;

    private Long companyId;
    private RequestPostProcessor owner;   // 공고 작성자
    private RequestPostProcessor other;   // 작성자가 아닌 다른 회원

    @BeforeEach
    void setUp() {

        Company company = Company.builder().name("데브잡").location("서울").build();
        companyId = companyRepository.save(company).getId();

        Member ownerMember = memberRepository.save(
                Member.builder().email("owner@devjob.kr").password("pw").role(Role.USER).build());
        Member otherMember = memberRepository.save(
                Member.builder().email("other@devjob.kr").password("pw").role(Role.USER).build());
        this.owner = asUser(ownerMember);
        this.other = asUser(otherMember);
    }

    private RequestPostProcessor asUser(Member member) {
        CustomUserDetails principal = new CustomUserDetails(member);
        return authentication(new UsernamePasswordAuthenticationToken(
                principal, null, principal.getAuthorities()));
    }

    @Test
    @DisplayName("채용 공고 등록(인증) → 201 Created + Location 헤더")
    void createJob() throws Exception {
        JobCreateRequest req = new JobCreateRequest("백엔드 개발자", companyId, 5000, "Spring Boot 백엔드 개발자 모집", null);

        mockMvc.perform(post("/api/jobs").with(owner)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.title").value("백엔드 개발자"))
                .andExpect(jsonPath("$.data.companyName").value("데브잡"))
                .andDo(print());
    }

    @Test
    @DisplayName("인증 없이 공고 등록 → 거부(4xx)")
    void createJobWithoutAuth() throws Exception {
        JobCreateRequest req = new JobCreateRequest("백엔드 개발자", companyId, 5000, "모집", null);

        mockMvc.perform(post("/api/jobs")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().is4xxClientError());
    }

    @Test
    @DisplayName("공고 조회는 비로그인도 가능 → 존재하지 않으면 404")
    void getOneNotFound() throws Exception {
        mockMvc.perform(get("/api/jobs/99999"))
                .andExpect(status().isNotFound());
    }

    @Test
    @DisplayName("작성자가 등록 → 수정 → 삭제 흐름")
    void updateAndDeleteJob() throws Exception {
        Long jobId = createJobBy(owner, "프론트엔드 개발자", "React 개발자 모집");

        JobUpdateRequest updateReq = new JobUpdateRequest("프론트엔드 개발자(시니어)", "React/TypeScript 시니어 개발자 모집", 6000, null);
        mockMvc.perform(put("/api/jobs/" + jobId).with(owner)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(updateReq)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.title").value("프론트엔드 개발자(시니어)"))
                .andExpect(jsonPath("$.data.salary").value(6000));

        mockMvc.perform(delete("/api/jobs/" + jobId).with(owner))
                .andExpect(status().isNoContent());

        mockMvc.perform(get("/api/jobs/" + jobId))
                .andExpect(status().isNotFound());
    }

    @Test
    @DisplayName("작성자가 아닌 회원의 수정·삭제 → 403 Forbidden")
    void nonOwnerCannotModify() throws Exception {
        Long jobId = createJobBy(owner, "데이터 엔지니어", "Spark 채용");

        JobUpdateRequest updateReq = new JobUpdateRequest("변경 시도", "남의 공고", 1000, null);
        mockMvc.perform(put("/api/jobs/" + jobId).with(other)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(updateReq)))
                .andExpect(status().isForbidden());

        mockMvc.perform(delete("/api/jobs/" + jobId).with(other))
                .andExpect(status().isForbidden());
    }

    @Test
    @DisplayName("채용 공고 검색 → keyword로 제목/회사명 통합 검색(공개)")
    void searchJobs() throws Exception {
        createJobBy(owner, "백엔드 신입 개발자", "Java/Spring 신입 채용");

        mockMvc.perform(get("/api/jobs/search").param("keyword", "백엔드"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.content[0].title").value("백엔드 신입 개발자"));
    }

    private Long createJobBy(RequestPostProcessor user, String title, String desc) throws Exception {
        JobCreateRequest req = new JobCreateRequest(title, companyId, 4000, desc, null);
        String response = mockMvc.perform(post("/api/jobs").with(user)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isCreated())
                .andReturn().getResponse().getContentAsString();
        return objectMapper.readTree(response).get("data").get("id").asLong();
    }
}
