package incheon.cmm.g2f.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@RequiredArgsConstructor
public class GisServerRestUtils {

    private final RestTemplate restTemplate;
    private final String baseUrl;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private static final long SYNC_WAIT_MILLIS = 3000L;

    /**
     * 레이어를 GIS Server에 등록한다
     *
     * @param workspace 레작업 공간 이름
     * @param name      생성할 레이어 이름
     * @param storage   저장소 이름(스키마 이름)
     * @param tableName 원본 테이블 이름
     * @param srid      좌표계
     * @param minx      (선택) 경계 영역의 최소 X값
     * @param miny      (선택) 경계 영역의 최소 Y값
     * @param maxx      (선택) 경계 영역의 최대 X값
     * @param maxy      (선택) 경계 영역의 최대 Y값
     * @return 요청이 성공적으로 처리되어 HTTP 응답 코드가 2xx인 경우 {@code true}, 그렇지 않으면 {@code false}
     * @throws RestClientException REST 요청 수행 중 오류가 발생한 경우
     */
    public boolean publishLayer(String workspace, String name, String storage, String tableName,
                                Integer srid, Double minx, Double miny, Double maxx, Double maxy) throws RestClientException, JsonProcessingException {

        String url = baseUrl + "/rest/layer";

        Map<String, Object> body = new HashMap<>();
        body.put("name", name);
        body.put("workspace", workspace);
        body.put("storage", storage);
        body.put("tableName", tableName);
        body.put("serviceSrid", srid);

        if (minx != null && maxx != null && miny != null && maxy != null) {
            body.put("minx", minx);
            body.put("miny", miny);
            body.put("maxx", maxx);
            body.put("maxy", maxy);
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);

        try {
            ResponseEntity<?> response = restTemplate.postForEntity(url, entity, Map.class);
            return response.getStatusCode().is2xxSuccessful();
        } catch (HttpClientErrorException e) {
            if (e.getStatusCode().value() == 404) {
                String responseBodyString = e.getResponseBodyAsString();
                Map<String, Object> reponseBody = objectMapper.readValue(responseBodyString, Map.class);
                String code = (String) reponseBody.get("code");
                if ("T001".equals(code)) {
                    waitForSync(SYNC_WAIT_MILLIS);
                    body.put("minx", 746110.2599834986);
                    body.put("miny", 1881540.2538266997);
                    body.put("maxx", 937637.2836452124);
                    body.put("maxy", 2001987.241769713);
                    entity = new HttpEntity<>(body, headers);
                    try {
                        ResponseEntity<?> response =  restTemplate.postForEntity(url, entity, Map.class);
                        return response.getStatusCode().is2xxSuccessful();
                    } catch (HttpClientErrorException e1) {
                        throw e;
                    }
                }
            } else if (e.getStatusCode().value() == 400){
                String responseBodyString = e.getResponseBodyAsString();
                Map reponseBody = objectMapper.readValue(responseBodyString, Map.class);
                String code = (String) reponseBody.get("code");
                if ("CS002".equals(code) || "CS003".equals(code)) {
                    body.put("minx", 746110.2599834986);
                    body.put("miny", 1881540.2538266997);
                    body.put("maxx", 937637.2836452124);
                    body.put("maxy", 2001987.241769713);
                    entity = new HttpEntity<>(body, headers);
                    try {
                        ResponseEntity<?> response =  restTemplate.postForEntity(url, entity, Map.class);
                        return response.getStatusCode().is2xxSuccessful();
                    } catch (HttpClientErrorException e1) {
                        throw e;
                    }
                }
            }
            throw e;
        }
    }

    /**
     * 레이어를 GIS Server에 등록한다
     *
     * @param workspace 레작업 공간 이름
     * @param name      생성할 레이어 이름
     * @param storage   저장소 이름(스키마 이름)
     * @param tableName 원본 테이블 이름
     * @param srid      좌표계
     * @param minx      (선택) 경계 영역의 최소 X값
     * @param miny      (선택) 경계 영역의 최소 Y값
     * @param maxx      (선택) 경계 영역의 최대 X값
     * @param maxy      (선택) 경계 영역의 최대 Y값
     * @return 응답
     * @throws RestClientException REST 요청 수행 중 오류가 발생한 경우
     */
    public ResponseEntity<Map> publishLayerWithResponse(String workspace, String name, String storage, String tableName,
                                                        Integer srid, Double minx, Double miny, Double maxx, Double maxy) throws RestClientException, JsonProcessingException {

        String url = baseUrl + "/rest/layer";

        Map<String, Object> body = new HashMap<>();
        body.put("name", name);
        body.put("workspace", workspace);
        body.put("storage", storage);
        body.put("tableName", tableName);
        body.put("serviceSrid", srid);

        if (minx != null && maxx != null && miny != null && maxy != null) {
            body.put("minx", minx);
            body.put("miny", miny);
            body.put("maxx", maxx);
            body.put("maxy", maxy);
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);
        try {
            return restTemplate.postForEntity(url, entity, Map.class);
        } catch (HttpClientErrorException e) {
            if (e.getStatusCode().value() == 404) {
                String responseBodyString = e.getResponseBodyAsString();
                Map<String, Object> reponseBody = objectMapper.readValue(responseBodyString, Map.class);
                String code = (String) reponseBody.get("code");
                if ("T001".equals(code)) {
                    waitForSync(SYNC_WAIT_MILLIS);
                    body.put("minx", 746110.2599834986);
                    body.put("miny", 1881540.2538266997);
                    body.put("maxx", 937637.2836452124);
                    body.put("maxy", 2001987.241769713);
                    entity = new HttpEntity<>(body, headers);
                    try {
                        return restTemplate.postForEntity(url, entity, Map.class);
                    } catch (HttpClientErrorException e1) {
                        throw e;
                    }
                }
            } else if (e.getStatusCode().value() == 400){
                String responseBodyString = e.getResponseBodyAsString();
                Map reponseBody = objectMapper.readValue(responseBodyString, Map.class);
                String code = (String) reponseBody.get("code");
                if ("CS002".equals(code) || "CS003".equals(code)) {
                    body.put("minx", 746110.2599834986);
                    body.put("miny", 1881540.2538266997);
                    body.put("maxx", 937637.2836452124);
                    body.put("maxy", 2001987.241769713);
                    entity = new HttpEntity<>(body, headers);
                    try {
                        return restTemplate.postForEntity(url, entity, Map.class);
                    } catch (HttpClientErrorException e1) {
                        throw e;
                    }
                }
            }
            throw e;
        }
    }

    /**
     * [PUT] /rest/workspace/{workspaceName}/layer/{layerName}
     * 레이어 수정
     */
    public boolean updateLayer(String workspace, String name, Integer srid,
                               Double minx, Double miny, Double maxx, Double maxy) throws RestClientException {

        String url = baseUrl + "/rest/workspace/" + workspace + "/layer/" + name;

        Map<String, Object> body = new HashMap<>();
        if (srid != null) body.put("serviceSrid", srid);
        if (minx != null) body.put("minx", minx);
        if (miny != null) body.put("miny", miny);
        if (maxx != null) body.put("maxx", maxx);
        if (maxy != null) body.put("maxy", maxy);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);

        try {
            ResponseEntity<?> response = restTemplate.exchange(url, HttpMethod.PUT, entity, Map.class);
            return response.getStatusCode().is2xxSuccessful();
        } catch (HttpClientErrorException.NotFound e) {
            throw new RestClientException("레이어가 존재하지 않습니다: " + workspace + "/" + name, e);
        } catch (HttpClientErrorException e) {
            throw e;
        }
    }

    /**
     * [DELETE] /rest/workspace/{workspaceName}/layer/{layerName}
     * 레이어 삭제
     */
    public boolean deleteLayer(String workspace, String name) throws RestClientException {

        String url = baseUrl + "/rest/workspace/" + workspace + "/layer/" + name;

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Void> entity = new HttpEntity<>(headers);

        try {
            ResponseEntity<?> response = restTemplate.exchange(url, HttpMethod.DELETE, entity, Map.class);
            return response.getStatusCode().is2xxSuccessful();
        } catch (HttpClientErrorException.NotFound e) {
            return false;
        } catch (HttpClientErrorException e) {
            throw e;
        }
    }

    /**
     * [GET] /rest/workspace/{workspaceName}/layer/{layerName}
     * 레이어 정보 조회
     */
    public Map getLayerInfo(String workspace, String name) throws RestClientException {

        String url = baseUrl + "/rest/workspace/" + workspace + "/layer/" + name;

        try {
            ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                return response.getBody();
            }
            return null;

        } catch (HttpClientErrorException.NotFound e) {
            return null;
        } catch (HttpClientErrorException e) {
            throw e;
        }
    }

    public boolean addStyle(String name, String xml) throws RestClientException {

        String url = baseUrl + "/rest/style";

        Map<String, Object> body = new HashMap<>();
        body.put("name", name);
        body.put("xml", xml);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);

        try {
            ResponseEntity<?> response = restTemplate.postForEntity(url, entity, Map.class);
            return response.getStatusCode().is2xxSuccessful();
        } catch (HttpClientErrorException e) {
            throw e;
        }
    }

    public String getSld(String name) throws RestClientException {
        String url = baseUrl + "/rest/sld/" + name;
        ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
        if (response.getStatusCode().is2xxSuccessful()) {
            return response.getBody();
        } else {
            throw new RuntimeException("SLD 조회 실패: " + response.getStatusCode());
        }
    }

    public boolean deleteStyle(String name) {
        String url = baseUrl + "/rest/sld/" + name;
        try {
            ResponseEntity<String> response = restTemplate.exchange(
                    url,
                    HttpMethod.DELETE,
                    null,
                    String.class
            );
            return response.getStatusCode().is2xxSuccessful();
        } catch (HttpClientErrorException e) {
            throw e;
        }
    }

    private void waitForSync(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("대기 중 인터럽트 발생", e);
        }
    }
}