package dev.lions.user.manager.client;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.runtime.Startup;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.faulttolerance.Timeout;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RolesResource;
import org.keycloak.admin.client.resource.UsersResource;

import jakarta.ws.rs.NotFoundException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Implémentation du client Keycloak Admin
 * Utilise le bean Keycloak géré par Quarkus (quarkus-keycloak-admin-rest-client)
 * qui respecte la configuration Jackson (fail-on-unknown-properties=false)
 * Utilise Circuit Breaker, Retry et Timeout pour la résilience
 */
@ApplicationScoped
@Startup
@Slf4j
public class KeycloakAdminClientImpl implements KeycloakAdminClient {

    @Inject
    Keycloak keycloak;

    @ConfigProperty(name = "lions.keycloak.server-url")
    String serverUrl;

    @ConfigProperty(name = "lions.keycloak.admin-realm")
    String adminRealm;

    @ConfigProperty(name = "lions.keycloak.admin-client-id")
    String adminClientId;

    @ConfigProperty(name = "lions.keycloak.admin-username")
    String adminUsername;

    @PostConstruct
    void init() {
        log.info("========================================");
        log.info("Initialisation du client Keycloak Admin");
        log.info("========================================");
        log.info("Server URL: {}", serverUrl);
        log.info("Admin Realm: {}", adminRealm);
        log.info("Admin Client ID: {}", adminClientId);
        log.info("Admin Username: {}", adminUsername);
        log.info("✅ Client Keycloak initialisé via Quarkus CDI (connexion lazy)");
        log.info("La connexion sera établie lors de la première requête API");
    }

    @Override
    @Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
    @Timeout(value = 30, unit = ChronoUnit.SECONDS)
    @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
    public Keycloak getInstance() {
        return keycloak;
    }

    @Override
    @Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
    @Timeout(value = 30, unit = ChronoUnit.SECONDS)
    @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
    public RealmResource getRealm(String realmName) {
        try {
            return keycloak.realm(realmName);
        } catch (Exception e) {
            log.error("Erreur lors de la récupération du realm {}: {}", realmName, e.getMessage());
            throw new RuntimeException("Impossible de récupérer le realm: " + realmName, e);
        }
    }

    @Override
    @Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
    @Timeout(value = 30, unit = ChronoUnit.SECONDS)
    @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
    public UsersResource getUsers(String realmName) {
        return getRealm(realmName).users();
    }

    @Override
    @Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
    @Timeout(value = 30, unit = ChronoUnit.SECONDS)
    @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
    public RolesResource getRoles(String realmName) {
        return getRealm(realmName).roles();
    }

    @Override
    public boolean isConnected() {
        try {
            // getAccessTokenString() n'implique pas la désérialisation de ServerInfoRepresentation
            // (qui échoue sur le champ inconnu "cpuInfo" avec Keycloak 26+)
            keycloak.tokenManager().getAccessTokenString();
            return true;
        } catch (Exception e) {
            log.warn("Keycloak non connecté: {}", e.getMessage());
            return false;
        }
    }

    @Override
    public boolean realmExists(String realmName) {
        try {
            getRealm(realmName).roles().list();
            return true;
        } catch (NotFoundException e) {
            log.debug("Realm {} n'existe pas", realmName);
            return false;
        } catch (Exception e) {
            log.debug("Erreur lors de la vérification du realm {} (probablement il existe): {}",
                     realmName, e.getMessage());
            return true;
        }
    }

    @Override
    @Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
    @Timeout(value = 30, unit = ChronoUnit.SECONDS)
    @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
    public List<String> getAllRealms() {
        try {
            log.debug("Récupération de tous les realms depuis Keycloak");
            // Appel HTTP direct pour éviter l'erreur de désérialisation de RealmRepresentation
            // (champ bruteForceStrategy inconnu dans la version de la librairie cliente)
            String token = keycloak.tokenManager().getAccessTokenString();

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(serverUrl + "/admin/realms"))
                .header("Authorization", "Bearer " + token)
                .header("Accept", "application/json")
                .GET()
                .build();

            HttpResponse<String> response = HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() != 200) {
                throw new RuntimeException("Keycloak returned HTTP " + response.statusCode());
            }

            ObjectMapper mapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            List<Map<String, Object>> realmMaps = mapper.readValue(
                response.body(), new TypeReference<>() {});

            List<String> realms = realmMaps.stream()
                .map(r -> (String) r.get("realm"))
                .filter(r -> r != null)
                .collect(Collectors.toList());

            log.debug("Realms récupérés: {}", realms);
            return realms;
        } catch (Exception e) {
            log.error("Erreur lors de la récupération de tous les realms: {}", e.getMessage());
            throw new RuntimeException("Impossible de récupérer la liste des realms", e);
        }
    }

    @Override
    @Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
    @Timeout(value = 30, unit = ChronoUnit.SECONDS)
    @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
    public List<String> getRealmClients(String realmName) {
        try {
            log.debug("Récupération des clients du realm {}", realmName);
            String token = keycloak.tokenManager().getAccessTokenString();

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(serverUrl + "/admin/realms/" + realmName + "/clients"))
                .header("Authorization", "Bearer " + token)
                .header("Accept", "application/json")
                .GET()
                .build();

            HttpResponse<String> response = HttpClient.newHttpClient()
                .send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() != 200) {
                throw new RuntimeException("Keycloak returned HTTP " + response.statusCode());
            }

            ObjectMapper mapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            List<Map<String, Object>> clientMaps = mapper.readValue(
                response.body(), new TypeReference<>() {});

            List<String> clients = clientMaps.stream()
                .map(c -> (String) c.get("clientId"))
                .filter(c -> c != null)
                .collect(Collectors.toList());

            log.debug("Clients récupérés pour {}: {}", realmName, clients);
            return clients;
        } catch (Exception e) {
            log.error("Erreur lors de la récupération des clients du realm {}: {}", realmName, e.getMessage());
            throw new RuntimeException("Impossible de récupérer les clients du realm: " + realmName, e);
        }
    }

    @PreDestroy
    @Override
    public void close() {
        log.info("Fermeture de la connexion Keycloak...");
        // Le cycle de vie est géré par Quarkus CDI
    }

    @Override
    public void reconnect() {
        log.info("Reconnexion à Keycloak... (géré par Quarkus CDI)");
        // Le bean Keycloak est géré par Quarkus, pas de reconnexion manuelle nécessaire
    }
}
