package dev.lions.user.manager.client;

import io.quarkus.runtime.Startup;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
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.KeycloakBuilder;
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.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Implémentation du client Keycloak Admin
 * Utilise Circuit Breaker, Retry et Timeout pour la résilience
 */
@ApplicationScoped
@Startup
@Slf4j
public class KeycloakAdminClientImpl implements KeycloakAdminClient {

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

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

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

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

    @ConfigProperty(name = "lions.keycloak.admin-password", defaultValue = "")
    String adminPassword;

    @ConfigProperty(name = "lions.keycloak.connection-pool-size", defaultValue = "10")
    Integer connectionPoolSize;

    @ConfigProperty(name = "lions.keycloak.timeout-seconds", defaultValue = "30")
    Integer timeoutSeconds;

    private Keycloak keycloak;

    @PostConstruct
    void init() {
        // Ne pas initialiser si les propriétés essentielles sont vides (ex: en mode test)
        if (serverUrl == null || serverUrl.isEmpty()) {
            log.debug("Configuration Keycloak non disponible - mode test ou configuration manquante");
            this.keycloak = null;
            return;
        }

        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("Connection Pool Size: {}", connectionPoolSize);
        log.info("Timeout: {} secondes", timeoutSeconds);

        try {
            this.keycloak = KeycloakBuilder.builder()
                .serverUrl(serverUrl)
                .realm(adminRealm)
                .clientId(adminClientId)
                .username(adminUsername)
                .password(adminPassword)
                .build();

            log.info("✅ Client Keycloak initialisé (connexion lazy)");
            log.info("La connexion sera établie lors de la première requête API");
        } catch (Exception e) {
            log.warn("⚠️ Échec de l'initialisation du client Keycloak");
            log.warn("URL: {}", serverUrl);
            log.warn("Realm: {}", adminRealm);
            log.warn("Username: {}", adminUsername);
            log.warn("Message: {}", e.getMessage());
            // Ne pas bloquer le démarrage - la connexion sera tentée lors du premier appel
            this.keycloak = null;
        }
    }

    @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() {
        if (keycloak == null) {
            log.warn("Instance Keycloak null, tentative de réinitialisation...");
            init();
        }
        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 getInstance().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 {
            if (keycloak == null) {
                return false;
            }
            keycloak.serverInfo().getInfo();
            return true;
        } catch (Exception e) {
            log.warn("Keycloak non connecté: {}", e.getMessage());
            return false;
        }
    }

    @Override
    public boolean realmExists(String realmName) {
        try {
            // Essayer d'obtenir simplement la liste des rôles du realm
            // Si le realm n'existe pas, cela lancera une NotFoundException
            // Si le realm existe mais a des problèmes de désérialisation, on suppose qu'il existe
            getRealm(realmName).roles().list();
            return true;
        } catch (NotFoundException e) {
            log.debug("Realm {} n'existe pas", realmName);
            return false;
        } catch (Exception e) {
            // En cas d'erreur (comme bruteForceStrategy lors de .toRepresentation()),
            // on suppose que le realm existe car l'erreur indique qu'on a pu le contacter
            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 via API REST directe");
            
            // Obtenir un token d'accès pour l'API REST
            Keycloak keycloakInstance = getInstance();
            String accessToken = keycloakInstance.tokenManager().getAccessTokenString();
            
            // Utiliser un client HTTP REST pour appeler directement l'API Keycloak
            // et parser uniquement les noms des realms depuis le JSON
            Client client = ClientBuilder.newClient();
            try {
                String realmsUrl = serverUrl + "/admin/realms";
                
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> realmsJson = client.target(realmsUrl)
                    .request(MediaType.APPLICATION_JSON)
                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                    .get(List.class);
                
                List<String> realmNames = new ArrayList<>();
                if (realmsJson != null) {
                    for (Map<String, Object> realm : realmsJson) {
                        Object realmNameObj = realm.get("realm");
                        if (realmNameObj != null) {
                            String realmName = realmNameObj.toString();
                            if (!realmName.isEmpty()) {
                                realmNames.add(realmName);
                            }
                        }
                    }
                    realmNames.sort(String::compareTo);
                }
                
                log.info("Récupération réussie: {} realms trouvés", realmNames.size());
                return realmNames;
            } finally {
                client.close();
            }
        } catch (Exception e) {
            log.error("Erreur lors de la récupération des realms: {}", e.getMessage(), e);
            // En cas d'erreur, retourner une liste vide plutôt que des données fictives
            return Collections.emptyList();
        }
    }

    @PreDestroy
    @Override
    public void close() {
        if (keycloak != null) {
            log.info("Fermeture de la connexion Keycloak...");
            keycloak.close();
            keycloak = null;
        }
    }

    @Override
    public void reconnect() {
        log.info("Reconnexion à Keycloak...");
        close();
        init();
    }
}
