package incheon.product.common.geo;

import com.fasterxml.jackson.databind.ObjectMapper;
import incheon.com.cmm.exception.BusinessException;
import incheon.product.common.config.GeoViewProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * GIS 서버(MapPrimeServer) REST API 클라이언트.
 * 기존 GisServerRestUtils의 하드코딩을 제거하고 GeoViewProperties로 설정을 외부화.
 */
@Slf4j
@Component
public class GisServerClient {

    @Resource(name = "geoViewProperties")
    private GeoViewProperties properties;

    @Resource(name = "restTemplate")
    private RestTemplate restTemplate;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * GIS 서버에 레이어를 발행한다.
     * T001/CS002/CS003 에러 시 fallback bbox로 재시도.
     */
    public boolean publishLayer(String workspace, String name, String storage,
                                String tableName, Integer srid,
                                Double minx, Double miny, Double maxx, Double maxy)
            throws RestClientException {

        String url = getBaseRestUrl() + "/layer";
        Map<String, Object> body = buildPublishBody(workspace, name, storage, tableName, srid, minx, miny, maxx, maxy);
        HttpEntity<Map<String, Object>> entity = createJsonEntity(body);

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

    /**
     * GIS 서버에 레이어를 발행하고 응답을 반환한다.
     */
    public ResponseEntity<Map> publishLayerWithResponse(String workspace, String name, String storage,
                                                         String tableName, Integer srid,
                                                         Double minx, Double miny, Double maxx, Double maxy)
            throws RestClientException {

        String url = getBaseRestUrl() + "/layer";
        Map<String, Object> body = buildPublishBody(workspace, name, storage, tableName, srid, minx, miny, maxx, maxy);
        HttpEntity<Map<String, Object>> entity = createJsonEntity(body);

        try {
            return restTemplate.postForEntity(url, entity, Map.class);
        } catch (HttpClientErrorException e) {
            return handlePublishErrorWithResponse(e, url, body);
        }
    }

    /**
     * 레이어를 발행한다 (간편 버전).
     * storage는 설정(geoview.gis-server.storage)에서, bbox는 생략한다.
     *
     * @param workspace 워크스페이스명
     * @param name      서비스명
     * @param tableName 스키마.테이블명
     * @param srid      좌표계 SRID
     */
    public boolean publishLayer(String workspace, String name, String tableName, int srid)
            throws RestClientException {
        String storage = properties.getGisServer().getStorage();
        return publishLayer(workspace, name, storage, tableName, srid, null, null, null, null);
    }

    /**
     * 레이어를 수정한다 (간편 버전).
     * SRID/bbox 변경 없이 갱신만 수행한다.
     */
    public boolean updateLayer(String workspace, String name) throws RestClientException {
        return updateLayer(workspace, name, null, null, null, null, null);
    }

    /**
     * 레이어를 수정한다.
     */
    public boolean updateLayer(String workspace, String name, Integer srid,
                               Double minx, Double miny, Double maxx, Double maxy)
            throws RestClientException {

        String url = getLayerUrl(workspace, 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);

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

        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);
        }
    }

    /**
     * 레이어를 삭제한다.
     */
    public boolean deleteLayer(String workspace, String name) throws RestClientException {
        String url = getLayerUrl(workspace, name);
        HttpEntity<Void> entity = new HttpEntity<>(createJsonHeaders());

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

    /**
     * 레이어 정보를 조회한다.
     */
    public Map<String, Object> getLayerInfo(String workspace, String name) throws RestClientException {
        String url = getLayerUrl(workspace, 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;
        }
    }

    /**
     * 레이어 존재 여부를 확인한다.
     */
    public boolean layerExists(String workspace, String name) {
        return getLayerInfo(workspace, name) != null;
    }

    /**
     * 스타일(SLD)을 추가한다.
     */
    public boolean addStyle(String name, String xml) throws RestClientException {
        String url = getBaseRestUrl() + "/style";

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

        HttpEntity<Map<String, Object>> entity = createJsonEntity(body);
        ResponseEntity<?> response = restTemplate.postForEntity(url, entity, Map.class);
        return response.getStatusCode().is2xxSuccessful();
    }

    /**
     * SLD를 조회한다.
     */
    public String getSld(String name) throws RestClientException {
        String url = getBaseRestUrl() + "/sld/" + name;
        ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
        if (response.getStatusCode().is2xxSuccessful()) {
            String body = response.getBody();
            return body != null ? body : "";
        }
        throw new RestClientException("SLD 조회 실패: " + response.getStatusCode());
    }

    /**
     * SLD를 삭제한다.
     */
    public boolean deleteStyle(String name) throws RestClientException {
        String url = getBaseRestUrl() + "/sld/" + name;
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.DELETE, null, String.class);
        return response.getStatusCode().is2xxSuccessful();
    }

    // --- private helpers ---

    private String getBaseRestUrl() {
        String url = properties.getGisServer().getUrl();
        if (url == null || url.isBlank()) {
            throw new BusinessException("geoview.gis-server.url 설정이 필요합니다.");
        }
        return url + "/rest";
    }

    private String getLayerUrl(String workspace, String name) {
        return getBaseRestUrl() + "/workspace/" + workspace + "/layer/" + name;
    }

    private HttpHeaders createJsonHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return headers;
    }

    private HttpEntity<Map<String, Object>> createJsonEntity(Map<String, Object> body) {
        return new HttpEntity<>(body, createJsonHeaders());
    }

    private Map<String, Object> buildPublishBody(String workspace, String name, String storage,
                                                  String tableName, Integer srid,
                                                  Double minx, Double miny, Double maxx, Double maxy) {
        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);
        }
        return body;
    }

    private void applyFallbackBbox(Map<String, Object> body) {
        GeoViewProperties.BoundingBox bbox = properties.getGisServer().getFallbackBbox();
        body.put("minx", bbox.getMinx());
        body.put("miny", bbox.getMiny());
        body.put("maxx", bbox.getMaxx());
        body.put("maxy", bbox.getMaxy());
    }

    private boolean isRetryableError(HttpClientErrorException e, String... codes) {
        try {
            String responseBody = e.getResponseBodyAsString();
            Map<String, Object> parsed = objectMapper.readValue(responseBody, Map.class);
            String code = (String) parsed.get("code");
            for (String c : codes) {
                if (c.equals(code)) return true;
            }
        } catch (Exception ex) {
            log.warn("GIS 서버 에러 응답 파싱 실패 - status: {}", e.getStatusCode(), ex);
        }
        return false;
    }

    private boolean handlePublishError(HttpClientErrorException e, String url,
                                       Map<String, Object> body) {
        if (e.getStatusCode().value() == 404 && isRetryableError(e, "T001")) {
            waitForSync();
            applyFallbackBbox(body);
            try {
                ResponseEntity<?> response = restTemplate.postForEntity(url, createJsonEntity(body), Map.class);
                return response.getStatusCode().is2xxSuccessful();
            } catch (HttpClientErrorException e1) {
                throw e;
            }
        } else if (e.getStatusCode().value() == 400 && isRetryableError(e, "CS002", "CS003")) {
            applyFallbackBbox(body);
            try {
                ResponseEntity<?> response = restTemplate.postForEntity(url, createJsonEntity(body), Map.class);
                return response.getStatusCode().is2xxSuccessful();
            } catch (HttpClientErrorException e1) {
                throw e;
            }
        }
        throw e;
    }

    private ResponseEntity<Map> handlePublishErrorWithResponse(HttpClientErrorException e, String url,
                                                                Map<String, Object> body) {
        if (e.getStatusCode().value() == 404 && isRetryableError(e, "T001")) {
            waitForSync();
            applyFallbackBbox(body);
            try {
                return restTemplate.postForEntity(url, createJsonEntity(body), Map.class);
            } catch (HttpClientErrorException e1) {
                throw e;
            }
        } else if (e.getStatusCode().value() == 400 && isRetryableError(e, "CS002", "CS003")) {
            applyFallbackBbox(body);
            try {
                return restTemplate.postForEntity(url, createJsonEntity(body), Map.class);
            } catch (HttpClientErrorException e1) {
                throw e;
            }
        }
        throw e;
    }

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