package go.kr.project.test;

import com.fasterxml.jackson.databind.ObjectMapper;
import go.kr.project.api.model.VehicleApiResponseVO;
import go.kr.project.carInspectionPenalty.registration.mapper.CarFfnlgTrgtMapper;
import go.kr.project.carInspectionPenalty.registration.model.CarFfnlgTrgtVO;
import go.kr.project.carInspectionPenalty.registration.service.ComparisonService;
import go.kr.project.carInspectionPenalty.registration.service.impl.ComparisonServiceImpl;
import go.kr.project.system.user.mapper.UserMapper;
import go.kr.project.system.user.model.SystemUserVO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;

/**
 * ComparisonService 테스트
 *
 * <p>JSON 파일을 사용하여 API 연동 없이 비교 로직을 테스트합니다.</p>
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("과태료 대상 비교 서비스 테스트")
class ComparisonServiceTest {

    @Mock
    private CarFfnlgTrgtMapper carFfnlgTrgtMapper;

    @Mock
    private UserMapper userMapper;

    private ComparisonService comparisonService;
    private ObjectMapper objectMapper;

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    @BeforeEach
    void setUp() {
        comparisonService = new ComparisonServiceImpl(carFfnlgTrgtMapper, userMapper);
        objectMapper = new ObjectMapper();

        // 공통 Mock 설정: DB 업데이트는 항상 성공 (lenient 사용 - 모든 테스트에서 사용하지 않을 수 있음)
        lenient().when(carFfnlgTrgtMapper.update(any(CarFfnlgTrgtVO.class))).thenReturn(1);
    }

    @Test
    @DisplayName("상품용 테스트 - 소유자명에 '상품용' 포함")
    void testProductUse() throws IOException {
        // Given: JSON 파일에서 테스트 데이터 로드
        VehicleApiResponseVO apiResponse = loadJsonFile("test-data/product-use-case.json");
        CarFfnlgTrgtVO existingData = createExistingData("12가3456");

        // When: 비교 로직 실행
        String result = comparisonService.executeComparison(existingData, apiResponse, "USER001");

        // Then: 상품용(02)으로 처리되어야 함
        assertEquals("02", result, "상품용으로 처리되어야 합니다");
        assertEquals("02", existingData.getTaskPrcsSttsCd(), "업무 처리 상태 코드가 02여야 합니다");
        assertEquals(LocalDate.now().format(DATE_FORMATTER), existingData.getTaskPrcsYmd(),
                "처리 일자가 오늘 날짜여야 합니다");
        assertEquals("상품용", existingData.getCarBscMttrInqFlnm(),
                "자동차 기본 사항 조회 성명이 저장되어야 합니다");
        assertNull(existingData.getCarBscMttrInqSggCd(), "이첩 관련 필드는 null이어야 합니다");
        assertNull(existingData.getCarBscMttrInqSggNm(), "이첩 관련 필드는 null이어야 합니다");

        System.out.println("✅ 상품용 테스트 통과!");
        System.out.println("   - 차량번호: " + existingData.getVhclno());
        System.out.println("   - 처리 상태: " + existingData.getTaskPrcsSttsCd() + " (02=상품용)");
        System.out.println("   - 소유자명: " + existingData.getCarBscMttrInqFlnm());
    }

    @Test
    @DisplayName("이첩 테스트 - 법정동코드 불일치")
    void testTransfer() throws IOException {
        // Given: JSON 파일에서 테스트 데이터 로드
        VehicleApiResponseVO apiResponse = loadJsonFile("test-data/transfer-case.json");
        CarFfnlgTrgtVO existingData = createExistingData("34나5678");

        // Mock 설정: 사용자 조직코드 (법정동코드와 다름)
        SystemUserVO userInfo = new SystemUserVO();
        userInfo.setUserId("USER001");
        userInfo.setOrgCd("1100");  // 서울(1100) - API의 경기(4100)와 다름
        when(userMapper.selectUser("USER001")).thenReturn(userInfo);

        // Mock 설정: 시군구명 조회
        when(carFfnlgTrgtMapper.selectSggNmBySggCd("41000")).thenReturn("경기도");

        // When: 비교 로직 실행
        String result = comparisonService.executeComparison(existingData, apiResponse, "USER001");

        // Then: 이첩(03)으로 처리되어야 함
        assertEquals("03", result, "이첩으로 처리되어야 합니다");
        assertEquals("03", existingData.getTaskPrcsSttsCd(), "업무 처리 상태 코드가 03이어야 합니다");
        assertEquals(LocalDate.now().format(DATE_FORMATTER), existingData.getTaskPrcsYmd(),
                "처리 일자가 오늘 날짜여야 합니다");
        assertNull(existingData.getCarBscMttrInqFlnm(), "상품용 관련 필드는 null이어야 합니다");
        assertEquals("41000", existingData.getCarBscMttrInqSggCd(), "시군구 코드가 저장되어야 합니다");
        assertEquals("경기도", existingData.getCarBscMttrInqSggNm(), "시군구명이 저장되어야 합니다");

        System.out.println("✅ 이첩 테스트 통과!");
        System.out.println("   - 차량번호: " + existingData.getVhclno());
        System.out.println("   - 처리 상태: " + existingData.getTaskPrcsSttsCd() + " (03=이첩)");
        System.out.println("   - 시군구: " + existingData.getCarBscMttrInqSggNm() + "(" + existingData.getCarBscMttrInqSggCd() + ")");
        System.out.println("   - 사유: 법정동코드 불일치 (API:4100, 조직:1100)");
    }

    @Test
    @DisplayName("정상 처리 테스트 - 모든 비교 조건에 해당하지 않음")
    void testNormal() throws IOException {
        // Given: JSON 파일에서 테스트 데이터 로드
        VehicleApiResponseVO apiResponse = loadJsonFile("test-data/normal-case.json");
        CarFfnlgTrgtVO existingData = createExistingData("56다7890");

        // Mock 설정: 사용자 조직코드 (법정동코드와 같음)
        SystemUserVO userInfo = new SystemUserVO();
        userInfo.setUserId("USER001");
        userInfo.setOrgCd("1100");  // 서울(1100) - API도 서울(1100)로 일치
        when(userMapper.selectUser("USER001")).thenReturn(userInfo);

        // When: 비교 로직 실행
        String result = comparisonService.executeComparison(existingData, apiResponse, "USER001");

        // Then: null 반환 (비교 조건에 해당하지 않음)
        assertNull(result, "비교 조건에 해당하지 않으면 null을 반환해야 합니다");

        System.out.println("✅ 정상 처리 테스트 통과!");
        System.out.println("   - 차량번호: " + existingData.getVhclno());
        System.out.println("   - 처리 결과: 정상 (비교 조건 미해당)");
        System.out.println("   - 소유자명에 '상품용' 없음");
        System.out.println("   - 법정동코드 일치 (API:1100, 조직:1100)");
    }

    @Test
    @DisplayName("순서 테스트 - 상품용이 먼저 체크되어야 함")
    void testPriority() throws IOException {
        // Given: 상품용이면서 법정동코드도 불일치한 케이스
        VehicleApiResponseVO apiResponse = loadJsonFile("test-data/product-use-case.json");
        CarFfnlgTrgtVO existingData = createExistingData("12가3456");

        // Mock 설정: 사용자 조직코드를 다르게 설정 (이첩 조건도 만족)
        // 주의: 상품용이 먼저 체크되므로 이 stub은 실제로 사용되지 않을 수 있음 (lenient 사용)
        SystemUserVO userInfo = new SystemUserVO();
        userInfo.setUserId("USER001");
        userInfo.setOrgCd("4100");  // API는 1100이므로 법정동코드 불일치
        lenient().when(userMapper.selectUser("USER001")).thenReturn(userInfo);

        // When: 비교 로직 실행
        String result = comparisonService.executeComparison(existingData, apiResponse, "USER001");

        // Then: 상품용(02)으로 처리되어야 함 (상품용이 우선순위 높음)
        assertEquals("02", result, "상품용과 이첩 조건을 모두 만족해도 상품용이 우선입니다");
        assertEquals("02", existingData.getTaskPrcsSttsCd());

        System.out.println("✅ 순서 테스트 통과!");
        System.out.println("   - 상품용과 이첩 조건 모두 만족");
        System.out.println("   - 결과: " + existingData.getTaskPrcsSttsCd() + " (상품용 우선)");
    }

    /**
     * JSON 파일을 읽어서 VehicleApiResponseVO로 변환
     */
    private VehicleApiResponseVO loadJsonFile(String filePath) throws IOException {
        ClassPathResource resource = new ClassPathResource(filePath);
        return objectMapper.readValue(resource.getInputStream(), VehicleApiResponseVO.class);
    }

    /**
     * 테스트용 기존 데이터 생성
     */
    private CarFfnlgTrgtVO createExistingData(String vhclno) {
        CarFfnlgTrgtVO vo = new CarFfnlgTrgtVO();
        vo.setCarFfnlgTrgtId("CFT00000000000000001");
        vo.setVhclno(vhclno);
        vo.setOwnrNm("테스트");
        vo.setTaskPrcsSttsCd("01");  // 접수 상태
        vo.setRcptYmd(LocalDate.now().format(DATE_FORMATTER));
        return vo;
    }
}
