package ai.sentryagent.idp.services;

import ai.sentryagent.idp.AgentIdPException;
import ai.sentryagent.idp.models.IntrospectResponse;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
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.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

/**
 * Client for token introspection and revocation endpoints.
 * Uses form-encoded POST bodies (not JSON), per RFC 7009 / RFC 7662.
 */
public final class TokenClient {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private final String baseUrl;
    private final Supplier<String> tokenSupplier;
    private final HttpClient httpClient;

    public TokenClient(String baseUrl, Supplier<String> tokenSupplier, HttpClient httpClient) {
        this.baseUrl       = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
        this.tokenSupplier = tokenSupplier;
        this.httpClient    = httpClient;
    }

    // ─── Sync ─────────────────────────────────────────────────────────────────

    /** POST /api/v1/token/introspect (form-encoded) → 200 IntrospectResponse */
    public IntrospectResponse introspectToken(String accessToken) {
        String body = "token=" + encode(accessToken);
        HttpRequest req = buildFormRequest(baseUrl + "/api/v1/token/introspect", body, tokenSupplier.get());
        try {
            HttpResponse<String> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
            if (resp.statusCode() < 200 || resp.statusCode() >= 300) {
                throw AgentIdPException.fromApiError(resp.body(), resp.statusCode());
            }
            return MAPPER.readValue(resp.body(), IntrospectResponse.class);
        } catch (AgentIdPException e) {
            throw e;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw AgentIdPException.networkError(e);
        } catch (IOException e) {
            throw AgentIdPException.networkError(e);
        }
    }

    /** POST /api/v1/token/revoke (form-encoded) → 200 */
    public void revokeToken(String accessToken) {
        String body = "token=" + encode(accessToken);
        HttpRequest req = buildFormRequest(baseUrl + "/api/v1/token/revoke", body, tokenSupplier.get());
        try {
            HttpResponse<String> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
            if (resp.statusCode() < 200 || resp.statusCode() >= 300) {
                throw AgentIdPException.fromApiError(resp.body(), resp.statusCode());
            }
        } catch (AgentIdPException e) {
            throw e;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw AgentIdPException.networkError(e);
        } catch (IOException e) {
            throw AgentIdPException.networkError(e);
        }
    }

    // ─── Async ────────────────────────────────────────────────────────────────

    /** Async version of {@link #introspectToken}. */
    public CompletableFuture<IntrospectResponse> introspectTokenAsync(String accessToken) {
        return CompletableFuture.supplyAsync(tokenSupplier).thenCompose(token -> {
            String body = "token=" + encode(accessToken);
            HttpRequest req = buildFormRequest(baseUrl + "/api/v1/token/introspect", body, token);
            return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofString())
                .thenApply(resp -> {
                    if (resp.statusCode() < 200 || resp.statusCode() >= 300) {
                        throw AgentIdPException.fromApiError(resp.body(), resp.statusCode());
                    }
                    try {
                        return MAPPER.readValue(resp.body(), IntrospectResponse.class);
                    } catch (IOException e) {
                        throw new AgentIdPException("PARSE_ERROR", "Failed to parse introspect response: " + e.getMessage(), resp.statusCode());
                    }
                });
        });
    }

    /** Async version of {@link #revokeToken}. */
    public CompletableFuture<Void> revokeTokenAsync(String accessToken) {
        return CompletableFuture.supplyAsync(tokenSupplier).thenCompose(token -> {
            String body = "token=" + encode(accessToken);
            HttpRequest req = buildFormRequest(baseUrl + "/api/v1/token/revoke", body, token);
            return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofString())
                .thenApply(resp -> {
                    if (resp.statusCode() < 200 || resp.statusCode() >= 300) {
                        throw AgentIdPException.fromApiError(resp.body(), resp.statusCode());
                    }
                    return (Void) null;
                });
        });
    }

    // ─── Helpers ──────────────────────────────────────────────────────────────

    private HttpRequest buildFormRequest(String url, String formBody, String token) {
        return HttpRequest.newBuilder()
            .uri(URI.create(url))
            .POST(HttpRequest.BodyPublishers.ofString(formBody))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .header("Accept", "application/json")
            .header("Authorization", "Bearer " + token)
            .build();
    }

    private static String encode(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }
}
