/*
 * ZapApi.java - cliente Java da API de WhatsApp da PluguiZap (rotas /instances/{ID}/token/{TOKEN}).
 * Sem dependências: java.net.http. Java 11+.
 *
 *   ZapApi zap = new ZapApi("SEU_ID", "SEU_TOKEN", "SEU_CLIENT_TOKEN");
 *   ZapApi.Resposta r = zap.texto("79999998888", "Seu pedido saiu para entrega");
 *   if (r.ok()) System.out.println(r.campo("messageId")); else System.out.println(r.erro());
 *   zap.arquivo("79999998888", Path.of("boleto.pdf"), "Segue o boleto");
 *
 * As respostas vêm como texto JSON (r.json()). Para ler campos simples use r.campo("nome");
 * para objetos maiores, passe r.json() para a biblioteca de JSON que você já usa (Jackson, Gson...).
 */
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ZapApi {
    public static final String URL_PADRAO = "https://api.pluguizap.com.br";

    private final HttpClient http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(15)).build();
    private final String base;
    private final String clientToken;

    public ZapApi(String instanciaId, String token, String clientToken) { this(instanciaId, token, clientToken, URL_PADRAO); }

    public ZapApi(String instanciaId, String token, String clientToken, String urlBase) {
        this.base = urlBase.replaceAll("/+$", "") + "/instances/" + instanciaId + "/token/" + token;
        this.clientToken = clientToken;
    }

    /** Resultado de uma chamada: código HTTP e o JSON devolvido. */
    public static final class Resposta {
        private final int http;
        private final String json;
        public Resposta(int http, String json) { this.http = http; this.json = json == null ? "" : json; }
        public int http() { return http; }
        public String json() { return json; }
        public boolean ok() { return http >= 200 && http < 300; }
        /** Lê um campo de primeiro nível (texto, número ou booleano). */
        public String campo(String nome) {
            Matcher m = Pattern.compile("\"" + Pattern.quote(nome) + "\"\\s*:\\s*(\"((?:\\\\.|[^\"\\\\])*)\"|true|false|null|-?[0-9.]+)").matcher(json);
            if (!m.find()) return null;
            return m.group(2) != null ? m.group(2).replace("\\\"", "\"").replace("\\\\", "\\") : m.group(1);
        }
        public String erro() { String e = campo("error"); return ok() ? null : (e != null ? e : "HTTP " + http); }
    }

    // ---------- base ----------
    public static String tel(String t) {
        if (t.contains("-group") || t.contains("@")) return t;
        String d = t.replaceAll("\\D", "");
        return (d.length() == 10 || d.length() == 11) ? "55" + d : d;
    }

    /** Texto como string JSON (aspas, barras, quebras de linha e controle escapados). */
    public static String js(String s) {
        if (s == null) return "null";
        StringBuilder b = new StringBuilder("\"");
        for (char c : s.toCharArray()) {
            switch (c) {
                case '"': b.append("\\\""); break;
                case '\\': b.append("\\\\"); break;
                case '\n': b.append("\\n"); break;
                case '\r': b.append("\\r"); break;
                case '\t': b.append("\\t"); break;
                default: if (c < 0x20) b.append(String.format("\\u%04x", (int) c)); else b.append(c);
            }
        }
        return b.append('"').toString();
    }

    /** Chama qualquer rota. corpoJson pode ser null. */
    public Resposta req(String metodo, String rota, String corpoJson) {
        HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(base + "/" + rota))
            .timeout(Duration.ofSeconds(60))
            .header("Client-Token", clientToken)
            .header("Content-Type", "application/json")
            .header("Accept", "application/json")
            .method(metodo, corpoJson == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(corpoJson));
        try {
            HttpResponse<String> r = http.send(b.build(), HttpResponse.BodyHandlers.ofString());
            return new Resposta(r.statusCode(), r.body());
        } catch (IOException e) {
            return new Resposta(0, "{\"error\":" + js("Falha de conexão: " + e.getMessage()) + "}");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return new Resposta(0, "{\"error\":\"Interrompido\"}");
        }
    }

    // ---------- conexão ----------
    public Resposta status() { return req("GET", "status", null); }
    public boolean conectado() { return "true".equals(status().campo("connected")); }
    /** campo("value") = "data:image/png;base64,..." */
    public Resposta qrCode() { return req("GET", "qr-code/image", null); }
    public Resposta codigoTelefone(String telefone) { return req("GET", "phone-code/" + tel(telefone), null); }
    public Resposta reiniciar() { return req("GET", "restart", null); }
    public Resposta desconectar() { return req("GET", "disconnect", null); }

    // ---------- mensagens ----------
    public Resposta texto(String telefone, String mensagem) {
        return req("POST", "send-text", "{\"phone\":" + js(tel(telefone)) + ",\"message\":" + js(mensagem) + "}");
    }

    /** imagem: link público ou data URL base64 */
    public Resposta imagem(String telefone, String imagem, String legenda) {
        return req("POST", "send-image", "{\"phone\":" + js(tel(telefone)) + ",\"image\":" + js(imagem) + ",\"caption\":" + js(legenda) + "}");
    }

    /** documento: link público ou data URL base64 */
    public Resposta documento(String telefone, String documento, String nomeArquivo, String legenda) {
        int p = nomeArquivo.lastIndexOf('.');
        String ext = p >= 0 ? nomeArquivo.substring(p + 1).toLowerCase() : "pdf";
        return req("POST", "send-document/" + ext, "{\"phone\":" + js(tel(telefone)) + ",\"document\":" + js(documento)
            + ",\"fileName\":" + js(nomeArquivo) + ",\"caption\":" + js(legenda) + "}");
    }

    /** Envia um arquivo do disco. Imagens vão como imagem; o resto como documento. */
    public Resposta arquivo(String telefone, Path caminho, String legenda) throws IOException {
        String mime = Files.probeContentType(caminho);
        String nome = caminho.getFileName().toString();
        if (mime == null) mime = nome.toLowerCase().endsWith(".pdf") ? "application/pdf" : "application/octet-stream";
        String dados = "data:" + mime + ";base64," + Base64.getEncoder().encodeToString(Files.readAllBytes(caminho));
        return mime.startsWith("image/") ? imagem(telefone, dados, legenda) : documento(telefone, dados, nome, legenda);
    }

    public Resposta enquete(String telefone, String pergunta, List<String> opcoes) {
        StringBuilder l = new StringBuilder();
        for (String o : opcoes) l.append(l.length() > 0 ? "," : "").append("{\"name\":").append(js(o)).append("}");
        return req("POST", "send-poll", "{\"phone\":" + js(tel(telefone)) + ",\"message\":" + js(pergunta) + ",\"poll\":[" + l + "],\"pollMaxOptions\":1}");
    }

    /** Chega como menu numerado; a resposta volta no webhook em buttonsResponseMessage.buttonId ("1", "2"...). */
    public Resposta botoes(String telefone, String mensagem, List<String> botoes) {
        StringBuilder l = new StringBuilder();
        for (int i = 0; i < botoes.size(); i++) l.append(i > 0 ? "," : "").append("{\"id\":\"").append(i + 1).append("\",\"label\":").append(js(botoes.get(i))).append("}");
        return req("POST", "send-button-list", "{\"phone\":" + js(tel(telefone)) + ",\"message\":" + js(mensagem) + ",\"buttonList\":{\"buttons\":[" + l + "]}}");
    }

    public Resposta localizacao(String telefone, double latitude, double longitude, String nome, String endereco) {
        return req("POST", "send-location", "{\"phone\":" + js(tel(telefone)) + ",\"latitude\":" + latitude + ",\"longitude\":" + longitude
            + ",\"title\":" + js(nome) + ",\"address\":" + js(endereco) + "}");
    }

    public Resposta marcarLida(String telefone, String messageId) {
        return req("POST", "read-message", "{\"phone\":" + js(tel(telefone)) + ",\"messageId\":" + js(messageId) + "}");
    }

    // ---------- contatos e webhook ----------
    public boolean temWhatsapp(String telefone) { return "true".equals(req("GET", "phone-exists/" + tel(telefone), null).campo("exists")); }
    public Resposta webhookTodos(String url) { return req("PUT", "update-every-webhooks", "{\"value\":" + js(url) + "}"); }
}
