package vn.addy.geo.client;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * Minimal Java client for the Addy Geo API. Drop this file into your project (adjust the package),
 * ensure Jackson (com.fasterxml.jackson.core:jackson-databind) is on the classpath — Spring Boot
 * apps already have it — and use:
 *
 * <pre>{@code
 *   var geo = new AddyGeoClient("https://YOUR_HOST");
 *   List<AddyGeoClient.Region> provinces = geo.provinces();
 *   List<AddyGeoClient.Region> wards = geo.wards("1");     // wards of Ha Noi (province 1)
 *   AddyGeoClient.Region baDinh = geo.ward("4");           // Phuong Ba Dinh
 * }</pre>
 */
public class AddyGeoClient {

    private final String baseUrl;
    private final HttpClient http = HttpClient.newHttpClient();
    private final ObjectMapper mapper = new ObjectMapper();

    public AddyGeoClient(String baseUrl) {
        this.baseUrl = baseUrl.replaceAll("/+$", "");
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Region(long nationalCode, String name, String level, String divisionType,
                         String divisionTypeLabel, String codename, String phoneCode,
                         Long parentCode, String parentName, boolean active) {}

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record ProvinceTree(Region province, List<Region> wards) {}

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record Envelope<T>(boolean success, String message, T data) {}

    public List<Region> provinces() {
        return get("/provinces", new TypeReference<Envelope<List<Region>>>() {}).data();
    }

    public List<Region> wards(String provinceCode) {
        return get("/provinces/" + enc(provinceCode) + "/wards",
                new TypeReference<Envelope<List<Region>>>() {}).data();
    }

    public ProvinceTree tree(String provinceCode) {
        return get("/provinces/" + enc(provinceCode) + "/tree",
                new TypeReference<Envelope<ProvinceTree>>() {}).data();
    }

    /** Resolve a province by its number. */
    public Region province(String code) {
        return get("/provinces/" + enc(code), new TypeReference<Envelope<Region>>() {}).data();
    }

    /** Resolve a ward by its number (includes its parent province). */
    public Region ward(String code) {
        return get("/wards/" + enc(code), new TypeReference<Envelope<Region>>() {}).data();
    }

    /** Search; returns the raw page as a Map (content, page, totalElements, ...). */
    public Map<String, Object> search(String q, String level, String parent, int page, int size) {
        StringBuilder p = new StringBuilder("/regions/search?page=" + page + "&size=" + size);
        if (q != null) p.append("&q=").append(enc(q));
        if (level != null) p.append("&level=").append(enc(level));
        if (parent != null) p.append("&parent=").append(enc(parent));
        return get(p.toString(), new TypeReference<Envelope<Map<String, Object>>>() {}).data();
    }

    public List<Region> exportAll() {
        return get("/export", new TypeReference<Envelope<List<Region>>>() {}).data();
    }

    private <T> T get(String path, TypeReference<T> type) {
        try {
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(baseUrl + "/api/v1" + path))
                    .header("Accept", "application/json")
                    .GET().build();
            HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() >= 400) {
                throw new RuntimeException("Addy Geo HTTP " + response.statusCode() + ": " + response.body());
            }
            return mapper.readValue(response.body(), type);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("Addy Geo request failed: " + e.getMessage(), e);
        }
    }

    private static String enc(String v) {
        return URLEncoder.encode(v, StandardCharsets.UTF_8);
    }
}
