package com.emonster.taroaichat.service.llm.gemini;

import com.emonster.taroaichat.config.ApplicationProperties;
import com.emonster.taroaichat.service.llm.gemini.tools.GeminiToolManager;
import com.google.genai.Client;
import com.google.genai.types.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Google Gemini SDK client for AI interactions.
 * Provides both synchronous and asynchronous API calls with streaming support.
 */
@Service
public class GeminiClient {

    private static final Logger LOG = LoggerFactory.getLogger(GeminiClient.class);

    private final ApplicationProperties applicationProperties;
    private final GeminiToolManager geminiToolManager;

    public GeminiClient(ApplicationProperties applicationProperties, GeminiToolManager geminiToolManager) {
        this.applicationProperties = applicationProperties;
        this.geminiToolManager = geminiToolManager;
    }

    /**
     * Create a Gemini Client instance with current configuration.
     */
    private Client createClient() {
        ApplicationProperties.Gemini config = applicationProperties.getGemini();

        if (config.getApiKey() == null || config.getApiKey().isEmpty()) {
            throw new IllegalStateException("Gemini API key not configured.");
        }

        return Client.builder()
            .apiKey(config.getApiKey())
            .build();
    }

    /**
     * Generate content using Gemini API (synchronous).
     */
    public GeminiResponse generateContent(GeminiRequest request) {
        ApplicationProperties.Gemini config = applicationProperties.getGemini();

        if (!config.isEnabled()) {
            throw new IllegalStateException("Gemini is disabled in configuration.");
        }

        String modelName = request.model != null ? request.model : config.getModel().getPrimary();
        LOG.debug("Generating content with model: {}", modelName);
        GenerateContentResponse response;
        try (Client client = createClient()) {
            // Convert messages to proper SDK Content objects
            List<Content> contents = convertMessagesToContent(request.messages);

            // Create generation config
            GenerateContentConfig genConfig = GenerateContentConfig.builder()
                .temperature(config.getParameters().getTemperature())
                .topP(config.getParameters().getTopP())
                .topK(config.getParameters().getTopK())
                .maxOutputTokens(config.getParameters().getMaxTokens())
                .build();

            response = client.models.generateContent(modelName, contents, genConfig);
            LOG.debug("Gemini response received successfully");
            return convertToGeminiResponse(response);
        } catch (Exception e) {
            LOG.error("Error generating content with Gemini", e);
            throw new RuntimeException("Failed to generate content with Gemini", e);
        }
    }

    /**
     * Generate content using Gemini API (asynchronous).
     */
    public Mono<GeminiResponse> generateContentAsync(GeminiRequest request) {
        ApplicationProperties.Gemini config = applicationProperties.getGemini();

        if (!config.isEnabled()) {
            return Mono.error(new IllegalStateException("Gemini is disabled in configuration."));
        }

        String modelName = request.model != null ? request.model : config.getModel().getPrimary();
        LOG.debug("Generating content async with model: {}", modelName);

        return Mono.fromCallable(() -> generateContent(request))
            .doOnSuccess(response -> LOG.debug("Gemini async response received"))
            .doOnError(error -> LOG.error("Gemini async generation failed", error));
    }

    /**
     * Generate content with streaming using SDK's native streaming capabilities.
     */
    public Flux<String> generateContentStream(GeminiRequest request) {
        ApplicationProperties.Gemini config = applicationProperties.getGemini();

        if (!config.isEnabled()) {
            return Flux.error(new IllegalStateException("Gemini is disabled in configuration."));
        }

        String modelName = request.model != null ? request.model : config.getModel().getPrimary();
        LOG.debug("Generating streaming content with model: {}", modelName);

        // Use Flux.using to manage resource lifecycle properly
        return Flux.using(
            // Resource supplier - create the client
            () -> createClient(),
            // Function that creates the Flux from the resource
            client -> {
                try {
                    // Convert messages to proper SDK Content objects
                    List<Content> contents = convertMessagesToContent(request.messages);

                    // Create generation config
                    GenerateContentConfig genConfig = GenerateContentConfig.builder()
                        .temperature(config.getParameters().getTemperature())
                        .topP(config.getParameters().getTopP())
                        .topK(config.getParameters().getTopK())
                        .maxOutputTokens(config.getParameters().getMaxTokens())
                        .build();

                    // Get the response stream
                    var responseStream = client.models.generateContentStream(modelName, contents, genConfig);

                    // Convert ResponseStream to reactive Flux
                    return Flux.fromIterable(responseStream)
                        .map(response -> {
                            String text = extractTextFromResponse(response);
                            LOG.debug("Streaming chunk received: {} characters", text.length());
                            return text;
                        })
                        .filter(text -> !text.isEmpty())
                        .doOnSubscribe(subscription -> LOG.debug("Started streaming for model: {}", modelName))
                        .doOnComplete(() -> LOG.debug("Streaming completed for model: {}", modelName))
                        .doOnError(error -> LOG.error("Streaming error for model: {}", modelName, error))
                        .doFinally(signal -> {
                            try {
                                responseStream.close();
                            } catch (Exception e) {
                                LOG.debug("Error closing response stream", e);
                            }
                        });
                } catch (Exception e) {
                    LOG.error("Error creating stream with Gemini", e);
                    return Flux.error(new RuntimeException("Failed to create stream with Gemini", e));
                }
            },
            // Resource cleanup
            client -> {
                try {
                    client.close();
                } catch (Exception e) {
                    LOG.error("Error closing Gemini client", e);
                }
            }
        );
    }

    /**
     * Generate content with streaming and native tool support combined.
     * This method streams responses and handles tool calls within the same stream.
     * When tools are called, they are executed and results are integrated into the conversation.
     */
    public Flux<Map<String, Object>> generateContentStreamWithTools(GeminiRequest request, boolean enableTools) {
        return generateContentStreamWithToolsInternal(request, enableTools, 0);
    }

    /**
     * Internal method that supports retries for malformed function calls.
     */
    private Flux<Map<String, Object>> generateContentStreamWithToolsInternal(GeminiRequest request, boolean enableTools, int retryCount) {
        ApplicationProperties.Gemini config = applicationProperties.getGemini();

        if (!config.isEnabled()) {
            return Flux.error(new IllegalStateException("Gemini is disabled in configuration."));
        }

        String modelName = request.model != null ? request.model : config.getModel().getPrimary();
        LOG.debug("Generating streaming content with tools: model={}, enableTools={}, retryCount={}", modelName, enableTools, retryCount);

        return Flux.using(
            () -> createClient(),
            client -> {
                try {
                    List<Content> contents;
                    String systemInstruction = null;

                    // Check if system prompt is passed separately in the request
                    if (request.systemPrompt != null && !request.systemPrompt.isEmpty()) {
                        // Use the separately provided system prompt
                        systemInstruction = request.systemPrompt;
                        // Convert only non-system messages
                        contents = new ArrayList<>();
                        for (GeminiRequest.Message message : request.messages) {
                            if (!"system".equals(message.role)) {
                                String geminiRole = convertRole(message.role);
                                if (geminiRole != null) {
                                    Content content = Content.builder()
                                        .role(geminiRole)
                                        .parts(List.of(Part.fromText(message.content)))
                                        .build();
                                    contents.add(content);
                                }
                            }
                        }
                        LOG.debug("Using separately provided system prompt, converted {} messages", contents.size());
                    } else {
                        // Fall back to extracting system instruction from messages
                        ConversionResult conversionResult = convertMessagesToContentWithSystemInstruction(request.messages);
                        contents = conversionResult.contents;
                        systemInstruction = conversionResult.systemInstruction;
                        LOG.debug("Extracted system instruction from messages");
                    }

                    GenerateContentConfig.Builder configBuilder = GenerateContentConfig.builder()
                        .temperature(config.getParameters().getTemperature())
                        .topP(config.getParameters().getTopP())
                        .topK(config.getParameters().getTopK())
                        .maxOutputTokens(config.getParameters().getMaxTokens());

                    // Add system instruction if present
                    if (systemInstruction != null && !systemInstruction.isEmpty()) {
                        // Create a Content object for system instruction
                        Content systemContent = Content.builder()
                             // System instructions does not have a role
                            .parts(List.of(Part.fromText(systemInstruction)))
                            .build();
                        configBuilder.systemInstruction(systemContent);
                        LOG.info("Added system instruction to Gemini config: {} chars", systemInstruction.length());
                        LOG.debug("System instruction preview: {}", systemInstruction.substring(0, Math.min(300, systemInstruction.length())));
                    }

                    // Add tools if enabled
                    if (enableTools && geminiToolManager.hasTools()) {
                        List<Tool> tools = geminiToolManager.getToolsForGeminiApi();
                        configBuilder.tools(tools);
                        configBuilder.toolConfig(ToolConfig.builder()
                            .functionCallingConfig(FunctionCallingConfig.builder()
                                .mode(FunctionCallingConfigMode.Known.AUTO)
                                .build())
                            .build());
                        LOG.info("Added {} native Gemini tools to streaming request", tools.size());

                        // Log tool details
                        LOG.info("Available tools for Gemini: {}", geminiToolManager.getToolNames());
                    }

                    GenerateContentConfig genConfig = configBuilder.build();

                    // Log the exact request being sent
                    LOG.info("Sending request to Gemini - Model: {}, Messages: {}, Tools enabled: {}, Has system instruction: {}",
                        modelName, contents.size(), enableTools && geminiToolManager.hasTools(), systemInstruction != null && !systemInstruction.isEmpty());

                    var responseStream = client.models.generateContentStream(modelName, contents, genConfig);

                    // Create a stateful operator to handle stream termination properly
                    return Flux.<Map<String, Object>>create(sink -> {
                        try {
                            for (GenerateContentResponse response : responseStream) {
                                // Process the response
                                List<Map<String, Object>> results = new ArrayList<>();
                                
                                // Check finish reason FIRST before processing
                                boolean shouldTerminate = false;
                                try {
                                    var candidatesOpt = response.candidates();
                                    if (candidatesOpt.isPresent() && !candidatesOpt.get().isEmpty()) {
                                        var candidate = candidatesOpt.get().get(0);
                                        var finishReasonOpt = candidate.finishReason();
                                        if (finishReasonOpt.isPresent()) {
                                            var finishReason = finishReasonOpt.get();
                                            LOG.debug("Response finish reason: {}", finishReason);
                                            
                                            // Check if this is a terminal finish reason
                                            if (finishReason.knownEnum() != null && 
                                                finishReason.knownEnum() != FinishReason.Known.FINISH_REASON_UNSPECIFIED) {
                                                LOG.info("Stream will terminate after this chunk due to finish reason: {}", finishReason);
                                                shouldTerminate = true;
                                            }
                                        }
                                    }
                                } catch (Exception e) {
                                    LOG.error("Error checking finish reason: {}", e.getMessage());
                                }
                                
                                // Now process the response content
                                try {
                                    // Log the response details for debugging
                                    LOG.debug("=== RAW RESPONSE ANALYSIS ===");
                                    var candidatesOpt = response.candidates();
                                    if (candidatesOpt.isPresent() && !candidatesOpt.get().isEmpty()) {
                                        var candidate = candidatesOpt.get().get(0);
                                        var finishReasonOpt = candidate.finishReason();
                                        if (finishReasonOpt.isPresent()) {
                                            LOG.debug("Processing response with finish reason: {}", finishReasonOpt.get());
                                        }
                                        
                                        // Log content structure before function call parsing
                                        var contentOpt = candidate.content();
                                        if (contentOpt.isPresent()) {
                                            var content = contentOpt.get();
                                            var partsOpt = content.parts();
                                            if (partsOpt.isPresent()) {
                                                var parts = partsOpt.get();
                                                LOG.debug("Response has {} parts", parts.size());
                                                
                                                for (int i = 0; i < parts.size(); i++) {
                                                    var part = parts.get(i);
                                                    LOG.debug("Part {}: hasFunctionCall={}, hasText={}", 
                                                        i, part.functionCall().isPresent(), part.text().isPresent());
                                                    
                                                    // Try to log raw function call data before parsing
                                                    if (part.functionCall().isPresent()) {
                                                        try {
                                                            // Try to access the function call to trigger parsing
                                                            var funcCall = part.functionCall().get();
                                                            LOG.debug("Function call parsed successfully: {}", funcCall.name().orElse("unknown"));
                                                        } catch (Exception fcParseEx) {
                                                            LOG.error("Function call parsing failed: {}", fcParseEx.getMessage());
                                                            // Log the class name and details to understand what we're dealing with
                                                            LOG.error("Part class: {}", part.getClass().getName());
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    LOG.debug("=== END RAW RESPONSE ANALYSIS ===");

                                    // First check for tool calls with better error handling
                                    var functionCalls = response.functionCalls();
                                    if (functionCalls != null && !functionCalls.isEmpty()) {
                                        LOG.info("Detected {} tool calls in stream", functionCalls.size());
                                        for (var call : functionCalls) {
                                            LOG.info("Tool call detected: {}", call.name().orElse("unknown"));

                                            // Log detailed function call info for debugging
                                            LOG.debug("Function call details - name: {}, args: {}",
                                                call.name().orElse("unknown"),
                                                call.args().orElse(Map.of()));
                                        }

                                        // Execute tools and emit results
                                        List<Map<String, Object>> toolResults = executeNativeTools(response);

                                        if (!toolResults.isEmpty()) {
                                            Map<String, Object> toolResult = new HashMap<>();
                                            toolResult.put("type", "tools");
                                            toolResult.put("toolResults", toolResults);
                                            results.add(toolResult);
                                        }
                                    }

                                    // Then check for text content
                                    String text = extractTextFromResponse(response);
                                    if (!text.isEmpty()) {
                                        Map<String, Object> textResult = new HashMap<>();
                                        textResult.put("type", "text");
                                        textResult.put("content", text);
                                        results.add(textResult);
                                    }

                                    // Emit results if any
                                    for (Map<String, Object> result : results) {
                                        sink.next(result);
                                    }
                                    
                                    // Check if we should terminate after this chunk
                                    if (shouldTerminate) {
                                        LOG.info("Terminating stream after processing final chunk");
                                        sink.complete();
                                        break; // Exit the loop
                                    }
                                    
                                } catch (IllegalArgumentException e) {
                                    if (e.getMessage() != null && e.getMessage().contains("MALFORMED_FUNCTION_CALL")) {
                                        LOG.error("=== MALFORMED_FUNCTION_CALL ERROR DETECTED ===");
                                        LOG.error("Error message: {}", e.getMessage());

                                        // For retry logic, we need to close the sink and return a retry flux
                                        final int maxRetries = 2;
                                        if (retryCount < maxRetries) {
                                            LOG.warn("MALFORMED_FUNCTION_CALL detected on attempt {}/{}. Will retry...", retryCount + 1, maxRetries + 1);
                                            sink.error(new RuntimeException("MALFORMED_FUNCTION_CALL - retry needed"));
                                            break;
                                        } else {
                                            LOG.error("Maximum retries ({}) exceeded for MALFORMED_FUNCTION_CALL. Continuing without tool call.", maxRetries);
                                            // Continue processing without the malformed tool call
                                        }
                                    } else {
                                        sink.error(e);
                                        break;
                                    }
                                } catch (Exception e) {
                                    LOG.error("Error processing response chunk", e);
                                    sink.error(e);
                                    break;
                                }
                            }
                            
                            // If we exited the loop normally, complete the sink
                            sink.complete();
                            
                        } catch (Exception e) {
                            LOG.error("Error in stream processing", e);
                            sink.error(e);
                        }
                    })
                    .onErrorResume(error -> {
                        // Handle retry for MALFORMED_FUNCTION_CALL
                        if (error.getMessage() != null && error.getMessage().contains("MALFORMED_FUNCTION_CALL - retry needed")) {
                            final int maxRetries = 2;
                            if (retryCount < maxRetries) {
                                LOG.info("Retrying after MALFORMED_FUNCTION_CALL error, attempt {}/{}", retryCount + 1, maxRetries + 1);
                                return generateContentStreamWithToolsInternal(request, enableTools, retryCount + 1);
                            }
                        }
                        return Flux.error(error);
                    })
                    .doOnSubscribe(s -> LOG.debug("Started streaming with tools for model: {}", modelName))
                    .doOnComplete(() -> LOG.debug("Streaming with tools completed"))
                    .doOnError(e -> LOG.error("Streaming with tools error", e))
                    .doFinally(signal -> {
                        try {
                            responseStream.close();
                        } catch (Exception e) {
                            LOG.debug("Error closing response stream", e);
                        }
                    });
                } catch (Exception e) {
                    LOG.error("Error creating stream with tools", e);
                    return Flux.error(new RuntimeException("Failed to create stream with tools", e));
                }
            },
            client -> {
                try {
                    client.close();
                } catch (Exception e) {
                    LOG.error("Error closing Gemini client", e);
                }
            }
        );
    }

    /**
     * Generate content with native Gemini tool support (synchronous).
     * Uses native Gemini SDK function calling capabilities.
     * Returns SDK GenerateContentResponse directly for clean integration.
     */
    public GenerateContentResponse generateContentWithNativeTools(GeminiRequest request, boolean enableTools) {
        ApplicationProperties.Gemini config = applicationProperties.getGemini();

        if (!config.isEnabled()) {
            throw new IllegalStateException("Gemini is disabled in configuration.");
        }

        String modelName = request.model != null ? request.model : config.getModel().getPrimary();
        LOG.debug("Generating content with native tools: model={}, tools={}", modelName, enableTools);

        try (Client client = createClient()) {
            // Convert messages to proper SDK Content objects
            List<Content> contents = convertMessagesToContent(request.messages);

            // Create generation config with optional tools
            GenerateContentConfig.Builder configBuilder = GenerateContentConfig.builder()
                .temperature(config.getParameters().getTemperature())
                .topP(config.getParameters().getTopP())
                .topK(config.getParameters().getTopK())
                .maxOutputTokens(config.getParameters().getMaxTokens());

            // Add native tools if enabled and available
            if (enableTools && geminiToolManager.hasTools()) {
                List<Tool> tools = geminiToolManager.getToolsForGeminiApi();
                configBuilder.tools(tools);

                // Configure function calling mode
                configBuilder.toolConfig(ToolConfig.builder()
                    .functionCallingConfig(FunctionCallingConfig.builder()
                        .mode(FunctionCallingConfigMode.Known.AUTO)
                        .build())
                    .build());

                LOG.debug("Added {} native Gemini tools to request", tools.size());
            }

            GenerateContentConfig genConfig = configBuilder.build();
            GenerateContentResponse response = client.models.generateContent(modelName, contents, genConfig);

            LOG.debug("Gemini native tool response received successfully");
            return response; // Return SDK response directly

        } catch (Exception e) {
            LOG.error("Error generating content with native Gemini tools", e);
            throw new RuntimeException("Failed to generate content with native Gemini tools", e);
        }
    }


    /**
     * Result class for message conversion with system instruction extraction.
     */
    private static class ConversionResult {
        final List<Content> contents;
        final String systemInstruction;

        ConversionResult(List<Content> contents, String systemInstruction) {
            this.contents = contents;
            this.systemInstruction = systemInstruction;
        }
    }

    /**
     * Convert messages to Content objects and extract system instruction separately.
     * This is used for methods that support system instruction in GenerateContentConfig.
     */
    private ConversionResult convertMessagesToContentWithSystemInstruction(List<GeminiRequest.Message> messages) {
        List<Content> contents = new ArrayList<>();
        StringBuilder systemContext = new StringBuilder();

        // Collect system messages
        for (GeminiRequest.Message message : messages) {
            if ("system".equals(message.role)) {
                if (systemContext.length() > 0) {
                    systemContext.append("\n\n");
                }
                systemContext.append(message.content);
                LOG.debug("Collected system message: {}", message.content.substring(0, Math.min(50, message.content.length())));
            }
        }

        // Process only user and assistant messages
        for (GeminiRequest.Message message : messages) {
            if ("system".equals(message.role)) {
                continue; // Skip system messages
            }

            String geminiRole = convertRole(message.role);
            if (geminiRole == null) {
                LOG.warn("Unknown role '{}', skipping message", message.role);
                continue;
            }

            // Create Content object without prepending system context
            Content content = Content.builder()
                .role(geminiRole)
                .parts(List.of(Part.fromText(message.content)))
                .build();

            contents.add(content);
        }

        LOG.debug("Converted {} messages to {} Content objects with system instruction", messages.size(), contents.size());
        return new ConversionResult(contents, systemContext.toString());
    }

    /**
     * Convert OpenRouter-style messages to Gemini SDK Content objects.
     * Handles proper role mapping and multi-turn conversation support.
     * This version prepends system messages to the first user message for compatibility.
     */
    private List<Content> convertMessagesToContent(List<GeminiRequest.Message> messages) {
        List<Content> contents = new ArrayList<>();
        StringBuilder systemContext = new StringBuilder();

        // First pass: Collect all system messages
        for (GeminiRequest.Message message : messages) {
            if ("system".equals(message.role)) {
                if (systemContext.length() > 0) {
                    systemContext.append("\n\n");
                }
                systemContext.append(message.content);
                LOG.debug("Added system message to context: {}", message.content.substring(0, Math.min(50, message.content.length())));
            }
        }

        // Second pass: Process user and assistant messages
        boolean firstUserMessage = true;
        for (GeminiRequest.Message message : messages) {
            if ("system".equals(message.role)) {
                continue; // Already processed
            }

            // Convert role to Gemini format
            String geminiRole = convertRole(message.role);
            if (geminiRole == null) {
                LOG.warn("Unknown role '{}', skipping message", message.role);
                continue;
            }

            // For the first user message, prepend system context
            String messageContent = message.content;
            if (firstUserMessage && "user".equals(geminiRole) && systemContext.length() > 0) {
                messageContent = systemContext.toString() + "\n\n" + message.content;
                firstUserMessage = false;
                LOG.debug("Prepended system context to first user message");
            }

            // Create Content object with proper role and text part
            Content content = Content.builder()
                .role(geminiRole)
                .parts(List.of(Part.fromText(messageContent)))
                .build();

            contents.add(content);
        }

        LOG.debug("Converted {} messages to {} Content objects", messages.size(), contents.size());
        return contents;
    }

    /**
     * Convert OpenRouter role to Gemini SDK role.
     *
     * @param role OpenRouter role ("user", "assistant", "system")
     * @return Gemini role ("user", "model") or null if unsupported
     */
    private String convertRole(String role) {
        return switch (role) {
            case "user" -> "user";
            case "assistant" -> "model";  // Gemini uses "model" instead of "assistant"
            case "system" -> null;     // System messages handled separately
            default -> null;
        };
    }

    /**
     * Convert Gemini response to our standard response format.
     */
    private GeminiResponse convertToGeminiResponse(GenerateContentResponse response) {
        GeminiResponse geminiResponse = new GeminiResponse();
        geminiResponse.candidates = new ArrayList<>();

        // Note: This is a simplified conversion. The actual SDK response structure
        // may be different and would need to be adjusted based on the real API.
        if (response != null) {
            GeminiResponse.Candidate responseCandidate = new GeminiResponse.Candidate();
            responseCandidate.content = new GeminiResponse.Content();
            responseCandidate.content.parts = new ArrayList<>();

            // Extract text content from response
            // This would need to be adjusted based on actual SDK response structure
            String text = extractTextFromResponse(response);
            if (!text.isEmpty()) {
                GeminiResponse.Part responsePart = new GeminiResponse.Part();
                responsePart.text = text;
                responseCandidate.content.parts.add(responsePart);
            }

            geminiResponse.candidates.add(responseCandidate);
        }

        return geminiResponse;
    }

    /**
     * Extract text from Gemini response using proper SDK methods.
     */
    private String extractTextFromResponse(GenerateContentResponse response) {
        if (response == null) {
            LOG.warn("Received null response from Gemini");
            return "";
        }

        try {
            // Get candidates from response using Optional
            var candidatesOptional = response.candidates();
            if (candidatesOptional.isEmpty()) {
                LOG.debug("No candidates found in Gemini response");
                return "";
            }

            var candidates = candidatesOptional.get();
            if (candidates.isEmpty()) {
                LOG.debug("Candidates list is empty");
                return "";
            }

            // Get first candidate (most relevant response)
            var candidate = candidates.get(0);
            if (candidate == null) {
                LOG.debug("First candidate is null");
                return "";
            }

            // Check if this is a function call response before attempting text extraction
            var contentOpt = candidate.content();
            if (contentOpt.isPresent()) {
                var partsOpt = contentOpt.get().parts();
                if (partsOpt.isPresent() && !partsOpt.get().isEmpty()) {
                    var firstPart = partsOpt.get().get(0);
                    if (firstPart.functionCall().isPresent()) {
                        LOG.debug("Response contains function call, not text");
                        return "";
                    }
                }
            }

            // Check finish reason to detect tool calls
            var finishReasonOpt = candidate.finishReason();
            if (finishReasonOpt.isPresent()) {
                var finishReason = finishReasonOpt.get();
                var finishReasonStr = finishReason.toString();
                if (finishReasonStr.contains("TOOL_CALL") || finishReason.knownEnum() == FinishReason.Known.STOP) {
                    LOG.debug("Response finished with reason: {}, may contain tool calls", finishReasonStr);
                    // Don't use response.text() as it may throw
                    // Instead, manually extract text from parts
                    if (contentOpt.isPresent()) {
                        var partsOpt = contentOpt.get().parts();
                        if (partsOpt.isPresent()) {
                            StringBuilder textBuilder = new StringBuilder();
                            for (var part : partsOpt.get()) {
                                var textOpt = part.text();
                                if (textOpt.isPresent()) {
                                    textBuilder.append(textOpt.get());
                                }
                            }
                            return textBuilder.toString();
                        }
                    }
                    return "";
                }
            }

            // Now safely try to use response.text() for regular text responses
            String text = response.text();
            if (text != null && !text.isEmpty()) {
                return text;
            }

            LOG.debug("No text content found in response");
            return "";

        } catch (IllegalArgumentException e) {
            // This happens when response contains function calls and we try to call text()
            LOG.debug("Response contains non-text content (likely function calls): {}", e.getMessage());

            // Try manual extraction as fallback
            try {
                var candidatesOptional = response.candidates();
                if (candidatesOptional.isPresent() && !candidatesOptional.get().isEmpty()) {
                    var candidate = candidatesOptional.get().get(0);
                    var contentOpt = candidate.content();
                    if (contentOpt.isPresent()) {
                        var partsOpt = contentOpt.get().parts();
                        if (partsOpt.isPresent()) {
                            StringBuilder textBuilder = new StringBuilder();
                            for (var part : partsOpt.get()) {
                                var textOpt = part.text();
                                if (textOpt.isPresent()) {
                                    textBuilder.append(textOpt.get());
                                }
                            }
                            return textBuilder.toString();
                        }
                    }
                }
            } catch (Exception innerE) {
                LOG.debug("Failed to manually extract text: {}", innerE.getMessage());
            }

            return "";
        } catch (Exception e) {
            LOG.error("Error extracting text from Gemini response", e);
            return "";
        }
    }

    /**
     * Helper method to execute native Gemini tools from a response.
     * Processes function calls and returns results for the service layer.
     */
    public List<Map<String, Object>> executeNativeTools(GenerateContentResponse response) {
        List<Map<String, Object>> toolResults = new ArrayList<>();

        if (response == null) {
            return toolResults;
        }

        var functionCalls = response.functionCalls();
        if (functionCalls == null) {
            return toolResults;
        }
        try {
            if (!functionCalls.isEmpty())
                for (FunctionCall functionCall : functionCalls) {
                    String toolName = functionCall.name().isPresent() ? functionCall.name().get() : null;
                    Map<String, Object> arguments = functionCall.args().isPresent() ? functionCall.args().get() : Map.of();

                    LOG.debug("Executing native Gemini tool: {} with args: {}", toolName, arguments);

                    // Execute tool using GeminiToolManager
                    var toolResult = geminiToolManager.executeTool(toolName, arguments);

                    // Convert to compatible format
                    Map<String, Object> result = Map.of(
                        "success", toolResult.isSuccess(),
                        "message", toolResult.getMessage(),
                        "data", toolResult.getData() != null ? toolResult.getData() : Map.of()
                    );

                    toolResults.add(result);
                }
        } catch (Exception e) {
            LOG.error("Error executing native Gemini tools", e);
        }

        return toolResults;
    }

    // Request/Response DTOs (compatible with OpenRouter format)
    public static class GeminiRequest {
        public String model;
        public List<Message> messages;
        public float temperature;
        public int maxTokens;
        public float topP;
        public float topK;  // Changed to float to match Gemini SDK
        public List<Map<String, Object>> tools;
        public String toolChoice;
        public Boolean stream;
        public String systemPrompt;  // For passing system prompt separately to Gemini

        public static class Message {
            public String role;
            public String content;

            public Message(String role, String content) {
                this.role = role;
                this.content = content;
            }
        }
    }

    public static class GeminiResponse {
        public List<Candidate> candidates;
        public UsageMetadata usageMetadata;

        public static class Candidate {
            public Content content;
            public String finishReason;
            public List<ToolCall> toolCalls;

            public static class ToolCall {
                public String id;
                public String type;
                public Function function;

                public static class Function {
                    public String name;
                    public String arguments;
                }
            }
        }

        public static class Content {
            public List<Part> parts;
            public String role;
        }

        public static class Part {
            public String text;
        }

        public static class UsageMetadata {
            public Integer promptTokenCount;
            public Integer candidatesTokenCount;
            public Integer totalTokenCount;
        }
    }

}
