package net.siegeln.cameleer.saas.identity;

import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

import java.time.Instant;
import java.util.Map;

/**
 * Authenticated client for cameleer3-server API calls.
 * Uses Logto M2M client_credentials grant with the Cameleer API resource.
 */
@Service
public class ServerApiClient {

    private static final Logger log = LoggerFactory.getLogger(ServerApiClient.class);
    private static final String API_RESOURCE = "https://api.cameleer.local";

    private final LogtoConfig config;
    private final RestClient tokenClient;

    private volatile String cachedToken;
    private volatile Instant tokenExpiry = Instant.MIN;

    public ServerApiClient(LogtoConfig config) {
        this.config = config;
        this.tokenClient = RestClient.builder().build();
    }

    public boolean isAvailable() {
        return config.isConfigured();
    }

    /**
     * Returns a RestClient pre-configured with server base URL and auth headers for GET requests.
     */
    public RestClient.RequestHeadersSpec<?> get(String uri) {
        return RestClient.create(config.getServerEndpoint())
                .get()
                .uri(uri)
                .header("Authorization", "Bearer " + getAccessToken())
                .header("X-Cameleer-Protocol-Version", "1");
    }

    /**
     * Returns a RestClient pre-configured with server base URL and auth headers for POST requests.
     */
    public RestClient.RequestBodySpec post(String uri) {
        return RestClient.create(config.getServerEndpoint())
                .post()
                .uri(uri)
                .header("Authorization", "Bearer " + getAccessToken())
                .header("X-Cameleer-Protocol-Version", "1");
    }

    /**
     * Push a license token to a server instance.
     */
    public void pushLicense(String serverEndpoint, String licenseToken) {
        RestClient.create(serverEndpoint)
                .post()
                .uri("/api/v1/admin/license")
                .header("Authorization", "Bearer " + getAccessToken())
                .header("X-Cameleer-Protocol-Version", "1")
                .contentType(MediaType.APPLICATION_JSON)
                .body(Map.of("token", licenseToken))
                .retrieve()
                .toBodilessEntity();
    }

    /** Health check for a specific tenant's server. */
    @SuppressWarnings("unchecked")
    public ServerHealthResponse getHealth(String serverEndpoint) {
        try {
            String url = serverEndpoint + "/api/v1/health";
            var resp = RestClient.create().get().uri(url)
                .header("Authorization", "Bearer " + getAccessToken())
                .header("X-Cameleer-Protocol-Version", "1")
                .retrieve()
                .body(Map.class);
            String status = resp != null ? String.valueOf(resp.get("status")) : "UNKNOWN";
            return new ServerHealthResponse("UP".equals(status), status);
        } catch (Exception e) {
            log.warn("Health check failed for {}: {}", serverEndpoint, e.getMessage());
            return new ServerHealthResponse(false, "DOWN");
        }
    }

    /** Push OIDC configuration to a tenant's server. */
    public void pushOidcConfig(String serverEndpoint, Map<String, Object> oidcConfig) {
        try {
            RestClient.create().put()
                .uri(serverEndpoint + "/api/admin/oidc")
                .header("Authorization", "Bearer " + getAccessToken())
                .header("X-Cameleer-Protocol-Version", "1")
                .header("Content-Type", "application/json")
                .body(oidcConfig)
                .retrieve()
                .toBodilessEntity();
            log.info("Pushed OIDC config to {}", serverEndpoint);
        } catch (Exception e) {
            log.error("Failed to push OIDC config to {}: {}", serverEndpoint, e.getMessage());
            throw new RuntimeException("OIDC config push failed: " + e.getMessage(), e);
        }
    }

    /** Get OIDC configuration from a tenant's server. */
    @SuppressWarnings("unchecked")
    public Map<String, Object> getOidcConfig(String serverEndpoint) {
        try {
            return RestClient.create().get()
                .uri(serverEndpoint + "/api/admin/oidc")
                .header("Authorization", "Bearer " + getAccessToken())
                .header("X-Cameleer-Protocol-Version", "1")
                .retrieve()
                .body(Map.class);
        } catch (Exception e) {
            log.warn("Failed to get OIDC config from {}: {}", serverEndpoint, e.getMessage());
            return Map.of();
        }
    }

    public record ServerHealthResponse(boolean healthy, String status) {}

    private synchronized String getAccessToken() {
        if (cachedToken != null && Instant.now().isBefore(tokenExpiry.minusSeconds(60))) {
            return cachedToken;
        }

        try {
            var response = tokenClient.post()
                    .uri(config.getLogtoEndpoint() + "/oidc/token")
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .body("grant_type=client_credentials"
                            + "&client_id=" + config.getM2mClientId()
                            + "&client_secret=" + config.getM2mClientSecret()
                            + "&resource=" + API_RESOURCE
                            + "&scope=server:admin")
                    .retrieve()
                    .body(JsonNode.class);

            cachedToken = response.get("access_token").asText();
            long expiresIn = response.get("expires_in").asLong();
            tokenExpiry = Instant.now().plusSeconds(expiresIn);
            log.info("Acquired cameleer3-server M2M access token");

            return cachedToken;
        } catch (Exception e) {
            log.error("Failed to get server API M2M token", e);
            throw new RuntimeException("Server API authentication failed", e);
        }
    }
}
