package dev.lions.unionflow.server.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.HexFormat;
import java.util.UUID;

/**
 * Service d'appel à l'API Wave Checkout (https://docs.wave.com/checkout).
 * Conforme à la spec : POST /v1/checkout/sessions, Wave-Signature si secret configuré.
 */
@ApplicationScoped
public class WaveCheckoutService {

    private static final Logger LOG = Logger.getLogger(WaveCheckoutService.class);

    @ConfigProperty(name = "wave.api.key", defaultValue = "")
    String apiKey;

    @ConfigProperty(name = "wave.api.base.url", defaultValue = "https://api.wave.com/v1")
    String baseUrl;

    @ConfigProperty(name = "wave.api.secret", defaultValue = "")
    String signingSecret;

    @ConfigProperty(name = "wave.redirect.base.url", defaultValue = "http://localhost:8080")
    String redirectBaseUrl;

    @ConfigProperty(name = "wave.mock.enabled", defaultValue = "false")
    boolean mockEnabled;

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * Crée une session Checkout Wave (spec : POST /v1/checkout/sessions).
     *
     * @param amount         Montant (string, pas de décimales pour XOF)
     * @param currency       Code ISO 4217 (ex: XOF)
     * @param successUrl     URL https de redirection après succès
     * @param errorUrl       URL https de redirection en cas d'erreur
     * @param clientRef      Référence client optionnelle (max 255 caractères)
     * @param restrictMobile Numéro E.164 optionnel pour restreindre le payeur
     * @return id de la session (cos-xxx) et wave_launch_url
     */
    public WaveCheckoutSessionResponse createSession(
            String amount,
            String currency,
            String successUrl,
            String errorUrl,
            String clientRef,
            String restrictMobile) throws WaveCheckoutException {

        boolean useMock = mockEnabled || apiKey == null || apiKey.trim().isBlank();
        if (useMock) {
            LOG.warn("Wave Checkout en mode MOCK (pas d'appel API Wave)");
            return createMockSession(successUrl, clientRef);
        }

        String base = (baseUrl == null || baseUrl.endsWith("/")) ? baseUrl.replaceAll("/+$", "") : baseUrl;
        if (!base.endsWith("/v1")) base = base + "/v1";
        String url = base + "/checkout/sessions";
        String body = buildRequestBody(amount, currency, successUrl, errorUrl, clientRef, restrictMobile);

        try {
            long timestamp = System.currentTimeMillis() / 1000;
            String waveSignature = null;
            if (signingSecret != null && !signingSecret.trim().isBlank()) {
                waveSignature = computeWaveSignature(timestamp, body);
            }

            java.net.http.HttpRequest.Builder requestBuilder = java.net.http.HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .header("Authorization", "Bearer " + apiKey)
                    .header("Content-Type", "application/json")
                    .timeout(Duration.ofSeconds(30))
                    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8));

            if (waveSignature != null) {
                requestBuilder.header("Wave-Signature", "t=" + timestamp + ",v1=" + waveSignature);
            }

            java.net.http.HttpClient client = java.net.http.HttpClient.newBuilder().build();
            java.net.http.HttpResponse<String> response = client.send(
                    requestBuilder.build(),
                    java.net.http.HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));

            if (response.statusCode() >= 400) {
                LOG.errorf("Wave Checkout API error: %d %s", response.statusCode(), response.body());
                throw new WaveCheckoutException("Wave API: " + response.statusCode() + " " + response.body());
            }

            JsonNode root = objectMapper.readTree(response.body());
            String id = root.has("id") ? root.get("id").asText() : null;
            String waveLaunchUrl = root.has("wave_launch_url") ? root.get("wave_launch_url").asText() : null;
            if (id == null || waveLaunchUrl == null) {
                throw new WaveCheckoutException("Réponse Wave invalide (id ou wave_launch_url manquant)");
            }
            return new WaveCheckoutSessionResponse(id, waveLaunchUrl);
        } catch (WaveCheckoutException e) {
            throw e;
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            throw new WaveCheckoutException("Erreur appel Wave Checkout: " + e.getMessage(), e);
        }
    }

    private String buildRequestBody(String amount, String currency, String successUrl, String errorUrl,
                                    String clientRef, String restrictMobile) {
        StringBuilder sb = new StringBuilder();
        sb.append("{\"amount\":\"").append(escapeJson(amount)).append("\"");
        sb.append(",\"currency\":\"").append(escapeJson(currency != null ? currency : "XOF")).append("\"");
        sb.append(",\"success_url\":\"").append(escapeJson(successUrl)).append("\"");
        sb.append(",\"error_url\":\"").append(escapeJson(errorUrl)).append("\"");
        if (clientRef != null && !clientRef.isBlank()) {
            String ref = clientRef.length() > 255 ? clientRef.substring(0, 255) : clientRef;
            sb.append(",\"client_reference\":\"").append(escapeJson(ref)).append("\"");
        }
        if (restrictMobile != null && !restrictMobile.isBlank()) {
            sb.append(",\"restrict_payer_mobile\":\"").append(escapeJson(restrictMobile)).append("\"");
        }
        sb.append("}");
        return sb.toString();
    }

    private static String escapeJson(String s) {
        if (s == null) return "";
        return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
    }

    /**
     * Spec Wave : payload = timestamp + body (raw string), HMAC-SHA256 avec signing secret.
     */
    private String computeWaveSignature(long timestamp, String body) throws NoSuchAlgorithmException, InvalidKeyException {
        String payload = timestamp + body;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(signingSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
        return HexFormat.of().formatHex(hash);
    }

    /**
     * Interroge l'état d'une session Wave Checkout (spec : GET /v1/checkout/sessions/:id).
     * Utilisé par le polling web pour détecter automatiquement la complétion du paiement.
     *
     * @param sessionId ID de session Wave (cos-xxx)
     * @return statut de la session (checkout_status, payment_status, transaction_id)
     */
    public WaveSessionStatusResponse getSession(String sessionId) throws WaveCheckoutException {
        boolean useMock = mockEnabled || apiKey == null || apiKey.trim().isBlank();
        if (useMock) {
            // En mock, on ne peut pas vraiment vérifier — retourner EN_COURS (polling s'arrête via /web-success)
            LOG.warnf("Wave getSession en mode MOCK — session %s", sessionId);
            return new WaveSessionStatusResponse(sessionId, "open", "processing", null);
        }

        String base = (baseUrl == null || baseUrl.endsWith("/")) ? baseUrl.replaceAll("/+$", "") : baseUrl;
        if (!base.endsWith("/v1")) base = base + "/v1";
        String url = base + "/checkout/sessions/" + sessionId;

        try {
            long timestamp = System.currentTimeMillis() / 1000;
            java.net.http.HttpRequest.Builder requestBuilder = java.net.http.HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .header("Authorization", "Bearer " + apiKey)
                    .header("Content-Type", "application/json")
                    .timeout(Duration.ofSeconds(15))
                    .GET();

            if (signingSecret != null && !signingSecret.trim().isBlank()) {
                String sig = computeWaveSignature(timestamp, "");
                requestBuilder.header("Wave-Signature", "t=" + timestamp + ",v1=" + sig);
            }

            java.net.http.HttpClient client = java.net.http.HttpClient.newBuilder().build();
            java.net.http.HttpResponse<String> response = client.send(
                    requestBuilder.build(),
                    java.net.http.HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));

            if (response.statusCode() >= 400) {
                throw new WaveCheckoutException("Wave API: " + response.statusCode() + " " + response.body());
            }

            JsonNode root = objectMapper.readTree(response.body());
            String checkoutStatus  = root.has("checkout_status")  ? root.get("checkout_status").asText()  : null;
            String paymentStatus   = root.has("payment_status")   ? root.get("payment_status").asText()   : null;
            String transactionId   = root.has("transaction_id")   ? root.get("transaction_id").asText()   : null;
            return new WaveSessionStatusResponse(sessionId, checkoutStatus, paymentStatus, transactionId);

        } catch (WaveCheckoutException e) {
            throw e;
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            throw new WaveCheckoutException("Erreur vérification session Wave: " + e.getMessage(), e);
        }
    }

    public String getRedirectBaseUrl() {
        return (redirectBaseUrl == null || redirectBaseUrl.trim().isBlank()) ? "http://localhost:8080" : redirectBaseUrl.trim();
    }

    /** Session mock pour tests : wave_launch_url = successUrl pour simuler le retour dans l'app. */
    private WaveCheckoutSessionResponse createMockSession(String successUrl, String clientRef) {
        String mockId = "cos-mock-" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
        return new WaveCheckoutSessionResponse(mockId, successUrl);
    }

    public static final class WaveSessionStatusResponse {
        public final String sessionId;
        /** "open" | "complete" | "expired" */
        public final String checkoutStatus;
        /** "processing" | "cancelled" | "succeeded" */
        public final String paymentStatus;
        /** ID transaction Wave (TCN...) — non-null si succeeded */
        public final String transactionId;

        public WaveSessionStatusResponse(String sessionId, String checkoutStatus, String paymentStatus, String transactionId) {
            this.sessionId      = sessionId;
            this.checkoutStatus = checkoutStatus;
            this.paymentStatus  = paymentStatus;
            this.transactionId  = transactionId;
        }

        public boolean isSucceeded() {
            return "succeeded".equals(paymentStatus) && "complete".equals(checkoutStatus);
        }

        public boolean isExpired() {
            return "expired".equals(checkoutStatus);
        }
    }

    public static final class WaveCheckoutSessionResponse {
        public final String id;
        public final String waveLaunchUrl;

        public WaveCheckoutSessionResponse(String id, String waveLaunchUrl) {
            this.id = id;
            this.waveLaunchUrl = waveLaunchUrl;
        }
    }

    public static class WaveCheckoutException extends RuntimeException {
        public WaveCheckoutException(String message) {
            super(message);
        }

        public WaveCheckoutException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
