package incheon.product.geoview2d.search.web;

import incheon.com.cmm.api.DefaultApiResponse;
import incheon.com.cmm.exception.BusinessException;
import incheon.product.geoview2d.search.service.SearchService;
import incheon.product.geoview2d.search.vo.AddressCoordinateVO;
import incheon.product.geoview2d.search.vo.AdmResultVO;
import incheon.product.geoview2d.search.vo.AdmSearchVO;
import incheon.product.geoview2d.search.vo.JibunSearchResultVO;
import incheon.product.geoview2d.search.vo.PoiVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 통합 검색 REST API 컨트롤러.
 * 주소, 행정구역, POI 검색을 통합 제공한다.
 *
 * 엔드포인트:
 *   - /api/v1/product/g2d/search/address/**  (주소 검색)
 *   - /api/v1/product/g2d/search/adm         (행정구역 검색)
 *   - /api/v1/product/g2d/search/poi/**       (POI 관리)
 */
@Slf4j
@RestController
@RequestMapping("/api/v1/product/g2d/search")
public class SearchApiController {

    private static final int MAX_PAGE_SIZE = 500;

    @Resource(name = "productSearchService")
    private SearchService searchService;

    // ========== 통합 검색 ==========

    /**
     * 통합 검색 엔드포인트.
     * type 파라미터로 검색 유형을 지정한다.
     *   - address : 지번 주소 검색
     *   - poi     : POI 키워드 검색
     *   - adm     : 행정구역 검색
     *   - all     : 전체 통합 검색 (기본값)
     */
    @GetMapping("")
    public ResponseEntity<DefaultApiResponse<Map<String, Object>>> search(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "all") String type,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {

        if (keyword == null || keyword.isBlank()) {
            throw new BusinessException("검색어는 필수입니다.", HttpStatus.BAD_REQUEST);
        }
        size = Math.min(Math.max(size, 1), MAX_PAGE_SIZE);

        Map<String, Object> result = new HashMap<>();

        if ("address".equals(type) || "all".equals(type)) {
            List<JibunSearchResultVO> address = searchService.getJibunSearchInfo(keyword, page, size);
            result.put("address", address);
        }

        if ("poi".equals(type) || "all".equals(type)) {
            List<PoiVO> poi = searchService.getPoiList(keyword, null, page, size);
            result.put("poi", poi);
            if ("poi".equals(type)) {
                result.put("totalElements", searchService.getPoiTotalCount(keyword, null));
            }
        }

        if ("adm".equals(type) || "all".equals(type)) {
            AdmSearchVO admSearch = new AdmSearchVO();
            admSearch.setSearchCondition(keyword);
            List<AdmResultVO> adm = searchService.getAdmList(admSearch);
            result.put("adm", adm);
        }

        result.put("keyword", keyword);
        result.put("type", type);

        return ResponseEntity.ok(DefaultApiResponse.success(result));
    }

    // ========== 주소 검색 ==========

    /**
     * 건물 좌표 조회 (단건).
     */
    @GetMapping("/address/coordinate")
    public ResponseEntity<DefaultApiResponse<AddressCoordinateVO>> getCoordinate(@RequestParam String bldgMngNo) {
        AddressCoordinateVO result = searchService.getCoordinateByBldgMngNo(bldgMngNo);
        if (result == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(DefaultApiResponse.error(404, "해당 건물관리번호의 좌표를 찾을 수 없습니다.", "Not Found"));
        }
        return ResponseEntity.ok(DefaultApiResponse.success(result));
    }

    /**
     * 건물 좌표 조회 (다건).
     */
    private static final int MAX_COORDINATE_REQUEST_SIZE = 500;

    @PostMapping("/address/coordinates")
    public ResponseEntity<DefaultApiResponse<List<AddressCoordinateVO>>> getCoordinates(@RequestBody List<String> bldgMngNoList) {
        if (bldgMngNoList == null || bldgMngNoList.isEmpty()) {
            throw new BusinessException("건물관리번호 목록이 비어있습니다.", HttpStatus.BAD_REQUEST);
        }
        if (bldgMngNoList.size() > MAX_COORDINATE_REQUEST_SIZE) {
            throw new BusinessException("한 번에 최대 " + MAX_COORDINATE_REQUEST_SIZE + "건까지 요청 가능합니다.", HttpStatus.BAD_REQUEST);
        }
        List<AddressCoordinateVO> result = searchService.getCoordinatesByBldgMngNo(bldgMngNoList);
        return ResponseEntity.ok(DefaultApiResponse.success(result));
    }

    /**
     * 지번 주소 검색.
     */
    @GetMapping("/address/jibun")
    public ResponseEntity<DefaultApiResponse<List<JibunSearchResultVO>>> searchJibun(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        if (keyword == null || keyword.isBlank()) {
            throw new BusinessException("검색어는 필수입니다.", HttpStatus.BAD_REQUEST);
        }
        size = Math.min(Math.max(size, 1), MAX_PAGE_SIZE);
        List<JibunSearchResultVO> result = searchService.getJibunSearchInfo(keyword, page, size);
        return ResponseEntity.ok(DefaultApiResponse.success(result));
    }

    // ========== 행정구역 검색 ==========

    /**
     * 행정구역 조회 (CTP/SIG/EMD/LI).
     */
    @GetMapping("/adm")
    public ResponseEntity<DefaultApiResponse<Map<String, Object>>> getAdmList(AdmSearchVO searchVO) {
        List<AdmResultVO> content = searchService.getAdmList(searchVO);

        Map<String, Object> response = new HashMap<>();
        response.put("content", content);
        if (searchVO.getPageSize() > 0) {
            long total = searchService.getAdmListTotCnt(searchVO);
            response.put("page", searchVO.getPageIndex());
            response.put("size", searchVO.getPageSize());
            response.put("totalElements", total);
            response.put("totalPages", (int) Math.ceil((double) total / searchVO.getPageSize()));
        }

        return ResponseEntity.ok(DefaultApiResponse.success(response));
    }

    // ========== POI 관리 ==========

    /**
     * POI 전체 목록.
     */
    @GetMapping("/poi")
    public ResponseEntity<DefaultApiResponse<List<PoiVO>>> getAllPoi() {
        return ResponseEntity.ok(DefaultApiResponse.success(searchService.getAllPoi()));
    }

    /**
     * POI 상세 조회.
     */
    @GetMapping("/poi/{nfId}")
    public ResponseEntity<DefaultApiResponse<PoiVO>> getPoi(@PathVariable String nfId) {
        PoiVO poi = searchService.getPoiById(nfId);
        if (poi == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(DefaultApiResponse.error(404, "POI를 찾을 수 없습니다: " + nfId, "Not Found"));
        }
        return ResponseEntity.ok(DefaultApiResponse.success(poi));
    }

    /**
     * POI 목록 조회 (페이징/검색).
     */
    @GetMapping("/poi/list")
    public ResponseEntity<DefaultApiResponse<Map<String, Object>>> getPoiList(
            @RequestParam(required = false) String searchKeyword,
            @RequestParam(required = false) String searchType,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {

        size = Math.min(Math.max(size, 1), MAX_PAGE_SIZE);
        List<PoiVO> content = searchService.getPoiList(searchKeyword, searchType, page, size);
        int total = searchService.getPoiTotalCount(searchKeyword, searchType);

        Map<String, Object> response = new HashMap<>();
        response.put("content", content);
        response.put("page", page);
        response.put("size", size);
        response.put("totalElements", total);
        response.put("totalPages", (int) Math.ceil((double) total / size));

        return ResponseEntity.ok(DefaultApiResponse.success(response));
    }

    /**
     * POI 생성.
     */
    @PostMapping("/poi")
    public ResponseEntity<DefaultApiResponse<Void>> createPoi(@Valid @RequestBody PoiVO poi) {
        searchService.createPoi(poi);
        return ResponseEntity.ok(DefaultApiResponse.success(null, "POI가 생성되었습니다."));
    }

    /**
     * POI 수정.
     */
    @PutMapping("/poi/{nfId}")
    public ResponseEntity<DefaultApiResponse<Void>> updatePoi(@PathVariable String nfId, @Valid @RequestBody PoiVO poi) {
        poi.setNfId(nfId);
        searchService.updatePoi(poi);
        return ResponseEntity.ok(DefaultApiResponse.success(null, "POI가 수정되었습니다."));
    }

    /**
     * POI 삭제.
     */
    @DeleteMapping("/poi/{nfId}")
    public ResponseEntity<DefaultApiResponse<Void>> deletePoi(@PathVariable String nfId) {
        searchService.deletePoi(nfId);
        return ResponseEntity.ok(DefaultApiResponse.success(null, "POI가 삭제되었습니다."));
    }
}
