package dev.lions.unionflow.server.payment.pispi;

import dev.lions.unionflow.server.api.payment.PaymentException;
import dev.lions.unionflow.server.payment.pispi.dto.Pacs002Response;
import dev.lions.unionflow.server.payment.pispi.dto.Pacs008Request;
import dev.lions.unionflow.server.payment.pispi.dto.PispiAlias;
import dev.lions.unionflow.server.payment.pispi.dto.PispiRtpRequest;
import dev.lions.unionflow.server.payment.pispi.dto.PispiRtpResponse;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import java.io.StringReader;
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.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;

/**
 * Client Business API PI-SPI (Plateforme Interopérable Système Paiement Instantané UEMOA).
 *
 * <p>Endpoints couverts :
 *
 * <ul>
 *   <li><strong>POST /transactions/initiate</strong> — initiation paiement pacs.008
 *   <li><strong>GET /transactions/{id}</strong> — statut transaction pacs.002
 *   <li><strong>POST /rtp/request</strong> — Request To Pay (pain.013) — appel cotisation
 *   <li><strong>GET /rtp/{id}</strong> — statut RTP (pain.014)
 *   <li><strong>POST /aliases</strong> — créer un alias téléphone/email → compte
 *   <li><strong>GET /aliases/{value}</strong> — résoudre un alias
 *   <li><strong>DELETE /aliases/{id}</strong> — révoquer un alias
 * </ul>
 *
 * <p>Toutes les requêtes utilisent l'auth 3-facteurs ({@link PispiAuth}) :
 *
 * <ol>
 *   <li>Bearer token OAuth2 (header {@code Authorization})
 *   <li>mTLS avec certif client (configuré sur le {@link HttpClient})
 *   <li>API Key (header {@code X-API-Key})
 * </ol>
 *
 * @since 2026-04-25 — RTP + alias + auth 3-facteurs ajoutés
 */
@Slf4j
@ApplicationScoped
public class PispiClient {

    @Inject PispiAuth pispiAuth;

    @ConfigProperty(name = "pispi.api.base-url",
            defaultValue = "https://sandbox.pispi.bceao.int/business-api/v1")
    String baseUrl;

    @ConfigProperty(name = "pispi.institution.code")
    Optional<String> institutionCodeOpt;

    String institutionCode;

    @jakarta.annotation.PostConstruct
    void init() {
        institutionCode = institutionCodeOpt.orElse("");
    }

    // ================================================================
    // Paiements ISO 20022 (pacs.008 / pacs.002)
    // ================================================================

    public Pacs002Response initiatePayment(Pacs008Request request) throws PaymentException {
        try {
            String token = pispiAuth.getAccessToken();
            String xmlBody = request.toXml();

            log.debug("PI-SPI initiatePayment endToEndId={}", request.getEndToEndId());

            HttpRequest httpRequest = baseRequestBuilder(URI.create(baseUrl + "/transactions/initiate"), token)
                    .header("Content-Type", "application/xml")
                    .POST(HttpRequest.BodyPublishers.ofString(xmlBody))
                    .build();

            HttpResponse<String> response = httpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
            checkStatus(response, "initiatePayment");
            return Pacs002Response.fromXml(response.body());
        } catch (PaymentException e) {
            throw e;
        } catch (Exception e) {
            throw new PaymentException("PISPI",
                    "Erreur lors de l'initiation du paiement PI-SPI : " + e.getMessage(), 503, e);
        }
    }

    public Pacs002Response getStatus(String transactionId) throws PaymentException {
        try {
            String token = pispiAuth.getAccessToken();
            log.debug("PI-SPI getStatus transactionId={}", transactionId);

            HttpRequest httpRequest = baseRequestBuilder(URI.create(baseUrl + "/transactions/" + transactionId), token)
                    .GET()
                    .build();

            HttpResponse<String> response = httpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
            checkStatus(response, "getStatus");
            return Pacs002Response.fromXml(response.body());
        } catch (PaymentException e) {
            throw e;
        } catch (Exception e) {
            throw new PaymentException("PISPI",
                    "Erreur lors de la récupération du statut PI-SPI : " + e.getMessage(), 503, e);
        }
    }

    // ================================================================
    // Request To Pay (RTP) — appels de cotisation
    // ================================================================

    /**
     * Initie un Request To Pay (pain.013) — la SFD demande un paiement au débiteur. Cas d'usage
     * principal : appel de cotisation envoyé en push vers le membre.
     */
    public PispiRtpResponse initiateRtp(PispiRtpRequest request) throws PaymentException {
        request.validate();
        try {
            String token = pispiAuth.getAccessToken();
            DateTimeFormatter iso = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

            String body = Json.createObjectBuilder()
                    .add("rtpId", request.rtpId())
                    .add("creditorInstitutionCode", nullSafe(request.creditorInstitutionCode()))
                    .add("creditorAccountNumber", nullSafe(request.creditorAccountNumber()))
                    .add("creditorName", nullSafe(request.creditorName()))
                    .add("debtorAlias", request.debtorAlias())
                    .add("amount", request.amount())
                    .add("currency", request.currency())
                    .add("purpose", nullSafe(request.purpose()))
                    .add("description", nullSafe(request.description()))
                    .add("requestedExecutionDate",
                            request.requestedExecutionDate() != null
                                    ? request.requestedExecutionDate().format(iso) : "")
                    .add("expiryDate",
                            request.expiryDate() != null ? request.expiryDate().format(iso) : "")
                    .build()
                    .toString();

            log.debug("PI-SPI initiateRtp rtpId={} amount={}", request.rtpId(), request.amount());

            HttpRequest httpRequest = baseRequestBuilder(URI.create(baseUrl + "/rtp/request"), token)
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(body))
                    .build();

            HttpResponse<String> response = httpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
            checkStatus(response, "initiateRtp");
            return parseRtpResponse(response.body());
        } catch (PaymentException e) {
            throw e;
        } catch (Exception e) {
            throw new PaymentException("PISPI",
                    "Erreur lors de l'initiation RTP PI-SPI : " + e.getMessage(), 503, e);
        }
    }

    public PispiRtpResponse getRtpStatus(String rtpId) throws PaymentException {
        try {
            String token = pispiAuth.getAccessToken();
            HttpRequest httpRequest = baseRequestBuilder(URI.create(baseUrl + "/rtp/" + rtpId), token)
                    .GET()
                    .build();
            HttpResponse<String> response = httpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
            checkStatus(response, "getRtpStatus");
            return parseRtpResponse(response.body());
        } catch (PaymentException e) {
            throw e;
        } catch (Exception e) {
            throw new PaymentException("PISPI",
                    "Erreur lors de la récupération du statut RTP PI-SPI : " + e.getMessage(), 503, e);
        }
    }

    // ================================================================
    // Alias (téléphone/email → compte)
    // ================================================================

    /** Résout un alias (ex: "+22507XXXXXXXX@unionflow") en informations de compte SFD. */
    public Optional<PispiAlias> resolveAlias(String aliasValue) throws PaymentException {
        try {
            String token = pispiAuth.getAccessToken();
            String encoded = URLEncoder.encode(aliasValue, StandardCharsets.UTF_8);
            HttpRequest httpRequest = baseRequestBuilder(URI.create(baseUrl + "/aliases/" + encoded), token)
                    .GET()
                    .build();
            HttpResponse<String> response = httpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() == 404) {
                return Optional.empty();
            }
            checkStatus(response, "resolveAlias");
            return Optional.of(parseAlias(response.body()));
        } catch (PaymentException e) {
            throw e;
        } catch (Exception e) {
            throw new PaymentException("PISPI",
                    "Erreur lors de la résolution d'alias PI-SPI : " + e.getMessage(), 503, e);
        }
    }

    /** Enregistre un nouvel alias (ex: créer "+225XXX@unionflow" à l'inscription d'un membre). */
    public PispiAlias createAlias(PispiAlias alias) throws PaymentException {
        try {
            String token = pispiAuth.getAccessToken();
            String body = Json.createObjectBuilder()
                    .add("aliasType", alias.aliasType())
                    .add("aliasValue", alias.aliasValue())
                    .add("institutionCode", nullSafe(alias.institutionCode()))
                    .add("accountNumber", nullSafe(alias.accountNumber()))
                    .add("accountHolderName", nullSafe(alias.accountHolderName()))
                    .build()
                    .toString();

            HttpRequest httpRequest = baseRequestBuilder(URI.create(baseUrl + "/aliases"), token)
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(body))
                    .build();

            HttpResponse<String> response = httpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
            checkStatus(response, "createAlias");
            return parseAlias(response.body());
        } catch (PaymentException e) {
            throw e;
        } catch (Exception e) {
            throw new PaymentException("PISPI",
                    "Erreur lors de la création d'alias PI-SPI : " + e.getMessage(), 503, e);
        }
    }

    /** Révoque un alias (ex: à la radiation d'un membre). */
    public void revokeAlias(String aliasId) throws PaymentException {
        try {
            String token = pispiAuth.getAccessToken();
            HttpRequest httpRequest = baseRequestBuilder(URI.create(baseUrl + "/aliases/" + aliasId), token)
                    .DELETE()
                    .build();
            HttpResponse<String> response = httpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() != 204) {
                checkStatus(response, "revokeAlias");
            }
        } catch (PaymentException e) {
            throw e;
        } catch (Exception e) {
            throw new PaymentException("PISPI",
                    "Erreur lors de la révocation d'alias PI-SPI : " + e.getMessage(), 503, e);
        }
    }

    // ================================================================
    // Helpers
    // ================================================================

    /** Construit le builder de requête HTTP avec auth 3-facteurs (Bearer + API Key + Institution). */
    private HttpRequest.Builder baseRequestBuilder(URI uri, String bearerToken) {
        return HttpRequest.newBuilder()
                .uri(uri)
                .timeout(Duration.ofSeconds(30))
                .header("Authorization", "Bearer " + bearerToken)
                .header("X-API-Key", pispiAuth.getApiKey())
                .header("X-Institution-Code", institutionCode)
                .header("Accept", "application/json, application/xml");
    }

    /** Retourne le client HTTP à utiliser : mTLS si configuré, sinon par défaut (mode dev). */
    private HttpClient httpClient() throws PaymentException {
        return pispiAuth.isConfigured() ? pispiAuth.getMtlsHttpClient() : HttpClient.newHttpClient();
    }

    private void checkStatus(HttpResponse<String> response, String operation) throws PaymentException {
        int status = response.statusCode();
        if (status >= 400) {
            throw new PaymentException("PISPI",
                    "PI-SPI " + operation + " HTTP " + status + " : " + response.body(), status);
        }
    }

    private PispiAlias parseAlias(String json) {
        try (JsonReader reader = Json.createReader(new StringReader(json))) {
            JsonObject obj = reader.readObject();
            return new PispiAlias(
                    obj.getString("aliasId", null),
                    obj.getString("aliasType", null),
                    obj.getString("aliasValue", null),
                    obj.getString("institutionCode", null),
                    obj.getString("accountNumber", null),
                    obj.getString("accountHolderName", null),
                    obj.getString("status", null));
        }
    }

    private PispiRtpResponse parseRtpResponse(String json) {
        try (JsonReader reader = Json.createReader(new StringReader(json))) {
            JsonObject obj = reader.readObject();
            String responseAt = obj.getString("responseAt", null);
            return new PispiRtpResponse(
                    obj.getString("rtpId", null),
                    obj.getString("status", null),
                    obj.getString("reasonCode", null),
                    obj.getString("reasonDescription", null),
                    responseAt != null ? LocalDateTime.parse(responseAt) : null,
                    obj.getString("settledTransactionId", null));
        }
    }

    private static String nullSafe(String s) {
        return s == null ? "" : s;
    }
}
