package com.emonster.taroaichat.service;

import com.emonster.taroaichat.domain.ChatMessage;
import com.emonster.taroaichat.domain.TarotSession;
import com.emonster.taroaichat.domain.enumeration.MessageSender;
import com.emonster.taroaichat.domain.enumeration.SessionStatus;
import com.emonster.taroaichat.repository.ChatMessageRepository;
import com.emonster.taroaichat.repository.TarotSessionRepository;
import com.emonster.taroaichat.service.dto.ChatMessageDTO;
import com.emonster.taroaichat.service.dto.ChatSendMessageDTO;
import com.emonster.taroaichat.service.dto.ChatStartReadingDTO;
import com.emonster.taroaichat.service.dto.sse.SseEvent;
import com.emonster.taroaichat.service.dto.sse.SseEventType;
import com.emonster.taroaichat.service.mapper.ChatMessageMapper;
import com.emonster.taroaichat.service.sse.SseService;
import com.emonster.taroaichat.service.llm.gemini.GeminiService;
import com.emonster.taroaichat.service.llm.openrouter.tools.AITool;
import com.emonster.taroaichat.service.llm.TarotPrompts;
import com.emonster.taroaichat.service.llm.dto.AIResponse;
import com.emonster.taroaichat.service.llm.dto.TarotCardData;
import com.emonster.taroaichat.service.dto.UserProfileDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;

/**
 * Service for managing chat sessions and messages.
 */
@Service
@Transactional
public class ChatService {

    private static final Logger log = LoggerFactory.getLogger(ChatService.class);

    private final ChatMessageRepository chatMessageRepository;
    private final TarotSessionRepository tarotSessionRepository;
    private final ChatMessageMapper chatMessageMapper;
    private final SseService sseService;
    private final GeminiService geminiService;
    private final TarotPrompts tarotPrompts;
    private final ObjectMapper objectMapper;
    private final UserProfileService userProfileService;

    public ChatService(
        ChatMessageRepository chatMessageRepository,
        TarotSessionRepository tarotSessionRepository,
        ChatMessageMapper chatMessageMapper,
        SseService sseService,
        GeminiService geminiService,
        TarotPrompts tarotPrompts,
        ObjectMapper objectMapper,
        UserProfileService userProfileService
    ) {
        this.chatMessageRepository = chatMessageRepository;
        this.tarotSessionRepository = tarotSessionRepository;
        this.chatMessageMapper = chatMessageMapper;
        this.sseService = sseService;
        this.geminiService = geminiService;
        this.tarotPrompts = tarotPrompts;
        this.objectMapper = objectMapper;
        this.userProfileService = userProfileService;
    }

    /**
     * Get chat history for a session.
     */
    @Transactional(readOnly = true)
    public List<ChatMessageDTO> getChatHistory(Long sessionId) {
        log.debug("Getting chat history for session: {}", sessionId);
        List<ChatMessage> messages = chatMessageRepository.findBySessionIdOrderByCreatedDate(sessionId);
        return chatMessageMapper.toDto(messages);
    }

    /**
     * Handle user joining a session.
     */
    public void joinSession(Long sessionId, String sseSessionId) {
        joinSession(sessionId, sseSessionId, null);
    }

    /**
     * Handle user joining a session with potential reconnection.
     */
    public void joinSession(Long sessionId, String sseSessionId, String lastEventId) {
        log.debug("User joining session: {}, lastEventId: {}", sessionId, lastEventId);

        // Get chat history
        List<ChatMessageDTO> chatHistory;

        if (lastEventId != null && lastEventId.startsWith("msg-")) {
            // Extract message ID from lastEventId (format: "msg-123")
            try {
                Long lastMessageId = Long.parseLong(lastEventId.substring(4));
                // Get only messages after the last received one
                chatHistory = getChatHistoryAfterMessageId(sessionId, lastMessageId);
                log.debug("Resuming from message ID: {}, found {} new messages", lastMessageId, chatHistory.size());
            } catch (NumberFormatException e) {
                log.warn("Invalid lastEventId format: {}, sending full history", lastEventId);
                chatHistory = getChatHistory(sessionId);
            }
        } else {
            // No lastEventId or invalid format, send full history
            chatHistory = getChatHistory(sessionId);
        }

        // Send session joined event with chat history
        Map<String, Object> data = new HashMap<>();
        data.put("sessionId", sessionId);
        data.put("chatHistory", chatHistory);
        data.put("resumed", lastEventId != null);

        SseEvent event = new SseEvent(sseSessionId, SseEventType.SESSION_JOINED, data);
        sseService.sendEvent(sseSessionId, event);
    }

    /**
     * Get chat history after a specific message ID.
     */
    private List<ChatMessageDTO> getChatHistoryAfterMessageId(Long sessionId, Long afterMessageId) {
        List<ChatMessage> messages = chatMessageRepository.findBySessionIdAndIdGreaterThanOrderByCreatedDate(sessionId, afterMessageId);
        return chatMessageMapper.toDto(messages);
    }

    /**
     * Process a user message.
     */
    public ChatMessageDTO sendMessage(ChatSendMessageDTO dto) {
        log.debug("Processing user message for session: {}", dto.getSessionId());

        // Verify session exists
        Optional<TarotSession> sessionOpt = tarotSessionRepository.findById(dto.getSessionId());
        if (sessionOpt.isEmpty()) {
            throw new IllegalArgumentException("Session not found: " + dto.getSessionId());
        }

        TarotSession session = sessionOpt.get();

        // Save user message
        ChatMessage userMessage = new ChatMessage();
        userMessage.setSession(session);
        userMessage.setSender(MessageSender.USER);
        userMessage.setMessage(dto.getMessage());
        userMessage.setTimestamp(Instant.now());
        userMessage = chatMessageRepository.save(userMessage);

        // Process AI response asynchronously
        processAiResponse(session, dto.getMessage());

        return chatMessageMapper.toDto(userMessage);
    }

    /**
     * Start a tarot reading.
     */
    public void startReading(ChatStartReadingDTO dto) {
        log.debug("Starting tarot reading for session: {}", dto.getSessionId());

        // Verify session exists
        Optional<TarotSession> sessionOpt = tarotSessionRepository.findById(dto.getSessionId());
        if (sessionOpt.isEmpty()) {
            throw new IllegalArgumentException("Session not found: " + dto.getSessionId());
        }

        TarotSession session = sessionOpt.get();
        session.setStatus(SessionStatus.STARTED);

        // Store the selected cards in the session for later interpretation
        if (dto.getSelectedCards() != null && !dto.getSelectedCards().isEmpty()) {
            String cardsJson = convertCardsToJson(dto.getSelectedCards());
            session.setCards(cardsJson);
        }

        tarotSessionRepository.save(session);

        // Send session update event
        Map<String, Object> data = Map.of(
            "sessionId", dto.getSessionId(),
            "status", SessionStatus.STARTED,
            "cardCount", dto.getCardCount()
        );

        SseEvent event = new SseEvent(String.valueOf(dto.getSessionId()), SseEventType.SESSION_UPDATE, data);
        sseService.sendEvent(String.valueOf(dto.getSessionId()), event);

        // NEW WORKFLOW: Send card acknowledgment greeting
        // Generate a greeting that acknowledges the cards without revealing them to AI
        if (dto.getSelectedCards() != null && !dto.getSelectedCards().isEmpty()) {
            processPostCardSelectionGreeting(session, dto.getSelectedCards());
        }
    }

    /**
     * Process AI response asynchronously using combined streaming approach.
     */
    @Async
    protected void processAiResponse(TarotSession session, String userMessage) {
        processAiResponse(session, userMessage, true);
    }

    /**
     * Process AI response asynchronously using combined streaming approach.
     */
    @Async
    protected void processAiResponse(TarotSession session, String userMessage, boolean enableTools) {
        String sessionId = String.valueOf(session.getId());

        // Use thread-safe containers for lambda expressions
        StringBuilder fullResponse = new StringBuilder();

        try {
            // Send typing indicator
            sseService.sendEvent(sessionId, new SseEvent(sessionId, SseEventType.AI_TYPING, null));

            // Get conversation history for context
            List<String> conversationHistory = getConversationHistory(session.getId());

            // Check if session has cards selected (for choosing appropriate system prompt)
            boolean hasCardsSelected = session.getCards() != null && !session.getCards().isEmpty();

            // Get card data if available
            List<TarotCardData> cardData = null;
            if (session.getCards() != null && !session.getCards().isEmpty()) {
                List<Map<String, Object>> storedCards = parseStoredCards(session.getCards());
                cardData = storedCards.stream()
                    .map(card -> new TarotCardData(
                        (String) card.get("name"),
                        (String) card.get("position"),
                        Boolean.TRUE.equals(card.get("isReversed"))
                    ))
                    .collect(Collectors.toList());
            }

            String sessionStatus = session.getStatus() != null ? session.getStatus().toString() : null;
            log.debug("Using combined streaming approach for session: {}, status: {}", sessionId, sessionStatus);

            // Get user profile data if available
            UserProfileDTO userProfile = null;
            if (session.getUserProfile() != null && session.getUserProfile().getId() != null) {
                userProfile = userProfileService.findOne(session.getUserProfile().getId()).orElse(null);
            }

            // Combined approach: Stream with tools enabled
            geminiService.generateChatResponseStreamWithCards(userMessage, conversationHistory, hasCardsSelected, enableTools, cardData, sessionStatus, userProfile)
                .doOnNext(result -> {
                    String type = (String) result.get("type");

                    if ("text".equals(type)) {
                        // Process text chunk from the Gemini stream
                        String chunk = (String) result.get("content");
                        if (chunk != null && !chunk.isEmpty()) {
                            // Regular content - append to full response
                            log.debug("Content chunk: '{}'", chunk);
                            synchronized (fullResponse) {
                                fullResponse.append(chunk);
                            }

                            // Split chunk into sentences and send them individually
                            // This regex splits on sentence endings (. ! ?) followed by space or end of string
                            // but keeps the punctuation with the sentence
                            String[] sentences = chunk.split("(?<=[.!?])\\s+");
                            for (String sentence : sentences) {
                                if (!sentence.isEmpty()) {
                                    // Send each sentence as a separate chunk
                                    Map<String, Object> data = new HashMap<>();
                                    data.put("content", sentence + " "); // Add space after sentence
                                    SseEvent event = new SseEvent(sessionId, SseEventType.AI_MESSAGE_CHUNK, data);
                                    sseService.sendEvent(sessionId, event);

                                    // Add a delay between sentences for more natural feel
                                    try {
                                        Thread.sleep(500); // 500ms between sentences
                                    } catch (InterruptedException e) {
                                        Thread.currentThread().interrupt();
                                    }
                                }
                            }
                        }
                    } else if ("tools".equals(type)) {
                        // Process tool results
                        List<Map<String, Object>> toolResults = (List<Map<String, Object>>) result.get("toolResults");
                        log.info("Processing {} tool results for session: {}", toolResults.size(), sessionId);

                        // Process each tool result with session context
                        for (Map<String, Object> toolResult : toolResults) {
                            processStreamingToolResult(sessionId, session, toolResult);
                        }
                    }
                })
                .doOnComplete(() -> {
                    // Stream complete
                    log.info("Combined streaming response completed for session: {}", sessionId);

                    // Process regular message content
                    String finalResponse;
                    synchronized (fullResponse) {
                        finalResponse = fullResponse.toString().trim();
                    }

                    log.debug("Final response length: {}", finalResponse.length());

                    if (!finalResponse.isEmpty()) {
                        // Save the AI message
                        ChatMessage aiMessage = new ChatMessage();
                        aiMessage.setSession(session);
                        aiMessage.setSender(MessageSender.AI);
                        aiMessage.setMessage(finalResponse);
                        aiMessage.setTimestamp(Instant.now());
                        chatMessageRepository.save(aiMessage);

                        // Send a final completion event
                        Map<String, Object> completionData = new HashMap<>();
                        completionData.put("isComplete", true);
                        SseEvent completionEvent = new SseEvent(sessionId, SseEventType.AI_MESSAGE, completionData);
                        sseService.sendEvent(sessionId, completionEvent);
                    }
                })
                .doOnError(error -> {
                    // This block executes if there is an error in the stream
                    log.error("Error during combined AI response stream for session: {}", sessionId, error);
                    sseService.sendError(sessionId, "Failed to process AI response", "AI_ERROR");
                })
                .subscribe(); // Subscribe to start the stream processing

        } catch (Exception e) {
            log.error("Error setting up combined AI response stream", e);
            sseService.sendError(sessionId, "Failed to process AI response", "AI_ERROR");
        }
    }

    /**
     * Get conversation history for a session.
     * Returns the most recent messages in chronological order (oldest to newest).
     */
    private List<String> getConversationHistory(Long sessionId) {
        List<ChatMessage> messages = chatMessageRepository.findBySessionIdOrderByCreatedDate(sessionId);

        // Get the last 20 messages (10 exchanges) for better context
        int messageCount = messages.size();
        int startIndex = Math.max(0, messageCount - 20);

        return messages.subList(startIndex, messageCount).stream()
            .map(msg -> (msg.getSender() == MessageSender.USER ? "User: " : "AI: ") + msg.getMessage())
            .collect(Collectors.toList());
    }

    /**
     * Process AI greeting after card selection (new workflow).
     */
    @Async
    protected void processPostCardSelectionGreeting(TarotSession session, List<ChatStartReadingDTO.SelectedCardData> selectedCards) {
        String sessionId = String.valueOf(session.getId());

        try {
            // Send typing indicator
            sseService.sendEvent(sessionId, new SseEvent(sessionId, SseEventType.AI_TYPING, null));

            // Generate AI greeting without revealing card details (prevent interpretation bias)
            // Pass null to ensure AI doesn't see card details in STARTED state
            geminiService.generatePostCardSelectionGreetingStream(null)
                .doOnNext(chunk -> {
                    // Process each text chunk from the Gemini stream
                    if (chunk != null && !chunk.isEmpty()) {
                        // Split chunk into sentences and send them individually
                        // This regex splits on sentence endings (. ! ?) followed by space or end of string
                        // but keeps the punctuation with the sentence
                        String[] sentences = chunk.split("(?<=[.!?])\\s+");
                        for (String sentence : sentences) {
                            if (!sentence.isEmpty()) {
                                // Send each sentence as a separate chunk
                                Map<String, Object> data = new HashMap<>();
                                data.put("content", sentence + " "); // Add space after sentence
                                SseEvent event = new SseEvent(sessionId, SseEventType.AI_MESSAGE_CHUNK, data);
                                sseService.sendEvent(sessionId, event);

                                // Add a delay between sentences for more natural feel
                                try {
                                    Thread.sleep(500); // 500ms between sentences
                                } catch (InterruptedException e) {
                                    Thread.currentThread().interrupt();
                                }
                            }
                        }
                    }
                })
                .doOnComplete(() -> {
                    // Transition to AWAITING_USER_CONTEXT after greeting is complete
                    updateSessionStatus(session, SessionStatus.AWAITING_USER_CONTEXT);

                    // Send completion event
                    Map<String, Object> completionData = new HashMap<>();
                    completionData.put("isComplete", true);
                    SseEvent completionEvent = new SseEvent(sessionId, SseEventType.AI_MESSAGE, completionData);
                    sseService.sendEvent(sessionId, completionEvent);

                    log.info("Post-card selection greeting sent for session: {}, transitioned to AWAITING_USER_CONTEXT", sessionId);
                })
                .doOnError(error -> {
                    log.error("Error during greeting stream for session: {}", sessionId, error);
                    sseService.sendError(sessionId, "Failed to process greeting", "AI_ERROR");
                })
                .subscribe();

        } catch (Exception e) {
            log.error("Error setting up greeting stream", e);
            sseService.sendError(sessionId, "Failed to process greeting", "AI_ERROR");
        }
    }

    /**
     * Convert selected cards to JSON string for storage.
     */
    private String convertCardsToJson(List<ChatStartReadingDTO.SelectedCardData> selectedCards) {
        try {
            // Create a simplified structure for storage
            List<Map<String, Object>> cardsData = selectedCards.stream()
                .map(card -> {
                    Map<String, Object> cardMap = new HashMap<>();
                    cardMap.put("name", card.getCard().getName());
                    cardMap.put("arcanaType", card.getCard().getArcanaType());
                    cardMap.put("cardNumber", card.getCard().getCardNumber());
                    cardMap.put("position", card.getPosition());
                    cardMap.put("isReversed", card.getIsReversed());
                    return cardMap;
                })
                .collect(Collectors.toList());

            return objectMapper.writeValueAsString(cardsData);
        } catch (JsonProcessingException e) {
            log.error("Error converting cards to JSON", e);
            // Return a basic string representation as fallback
            return selectedCards.stream()
                .map(card -> card.getPosition() + ": " + card.getCard().getName() +
                    (Boolean.TRUE.equals(card.getIsReversed()) ? " (Reversed)" : ""))
                .collect(Collectors.joining(", "));
        }
    }

    /**
     * Process streaming tool result and handle state transitions.
     */
    private void processStreamingToolResult(String sessionId, TarotSession session, Map<String, Object> toolResult) {
        try {
            boolean success = (boolean) toolResult.get("success");
            String message = (String) toolResult.get("message");
            Map<String, Object> toolData = (Map<String, Object>) toolResult.get("data");

            if (!success) {
                log.warn("Tool execution failed: {}", message);
                return;
            }

            // Extract tool name from message (format: "toolname executed successfully")
            String toolName = message.split(" ")[0];
            log.info("Processing streaming tool result: {} for session: {}", toolName, sessionId);

            // Handle specific tool calls with state transitions
            switch (toolName) {
                case "start_interpretation" -> {
                    // Transition to READY_FOR_INTERPRETATION state
                    updateSessionStatus(session, SessionStatus.READY_FOR_INTERPRETATION);

                    // Enable "Begin Interpretation" button
                    Map<String, Object> actionData = new HashMap<>();
                    actionData.put("userContext", toolData.get("userContext"));
                    actionData.put("overallTheme", toolData.get("overallTheme"));
                    actionData.put("readyToInterpret", toolData.get("readyToInterpret"));

                    SseEvent event = new SseEvent(sessionId, SseEventType.AI_INTERPRETATION_READY, actionData);
                    sseService.sendEvent(sessionId, event);
                    log.info("Streaming tool processed: start_interpretation, transitioned to READY_FOR_INTERPRETATION");
                }
                case "reveal_card" -> {
                    String cardPosition = (String) toolData.get("cardPosition");
                    String interpretation = (String) toolData.get("interpretation");

                    // Reveal specific card for interpretation - include actual card data
                    Map<String, Object> cardDataMap = new HashMap<>();
                    cardDataMap.put("cardPosition", toolData.get("cardPosition"));
                    cardDataMap.put("cardInterpretationNote", toolData.get("cardInterpretationNote"));

                    // Get actual card data from session
                    List<Map<String, Object>> storedCards = parseStoredCards(session.getCards());
                    Map<String, Object> actualCard = getCardByPosition(storedCards, cardPosition);

                    if (actualCard != null) {
                        cardDataMap.put("cardName", actualCard.get("name"));
                        cardDataMap.put("arcanaType", actualCard.get("arcanaType"));
                        cardDataMap.put("cardNumber", actualCard.get("cardNumber"));
                        cardDataMap.put("isReversed", actualCard.get("isReversed"));
                    } else {
                        log.warn("Could not find card data for position: {}", cardPosition);
                    }

                    SseEvent event = new SseEvent(sessionId, SseEventType.AI_CARD_REVEAL, cardDataMap);
                    sseService.sendEvent(sessionId, event);

                    // Store the interpretation immediately from the tool call
                    if (interpretation != null && !interpretation.trim().isEmpty()) {
                        updateCardInterpretation(session, cardPosition, interpretation.trim());
                        log.info("Stored interpretation from streaming tool for {} card in session {}", cardPosition, session.getId());

                        // Transition state after interpretation is stored
                        transitionAfterInterpretation(session, cardPosition);

                        // Send pseudo message to trigger AI to stream interpretation naturally
                        triggerInterpretationStreaming(sessionId, session, cardPosition);
                    } else {
                        log.warn("No interpretation provided in streaming reveal_card tool call for position: {}", cardPosition);
                    }

                    log.info("Streaming tool processed: reveal_card for position: {} with interpretation", cardPosition);
                }
                case "complete_reading" -> {
                    // Complete the reading with final summary
                    Map<String, Object> completionData = new HashMap<>();
                    completionData.put("readingSummary", toolData.get("readingSummary"));
                    completionData.put("empoweringConclusion", toolData.get("empoweringConclusion"));
                    completionData.put("followUpReady", toolData.get("followUpReady"));

                    // Save the reading summary to the database
                    String readingSummary = (String) toolData.get("readingSummary");
                    log.info("=== SAVING READING SUMMARY (STREAMING) ===");
                    log.info("Session ID: {}", sessionId);
                    log.info("Reading summary from tool: '{}'", readingSummary);
                    log.info("Reading summary length: {}", readingSummary != null ? readingSummary.length() : 0);
                    if (readingSummary != null && !readingSummary.trim().isEmpty()) {
                        session.setSummary(readingSummary.trim());
                        session.setStatus(SessionStatus.COMPLETED);
                        session.setCompletedAt(Instant.now());
                        tarotSessionRepository.save(session);
                        log.info("Reading summary saved to database for session: {}", sessionId);
                    } else {
                        log.warn("Cannot save empty reading summary for session: {}", sessionId);
                    }
                    log.info("=== END SAVING READING SUMMARY (STREAMING) ===");

                    SseEvent event = new SseEvent(sessionId, SseEventType.AI_READING_COMPLETE, completionData);
                    sseService.sendEvent(sessionId, event);
                    log.info("Streaming tool processed: complete_reading");

                    // Send pseudo message to trigger AI to stream closing message naturally
                    triggerCompletionStreaming(sessionId, session);
                }
                default -> {
                    log.warn("Unknown streaming tool call: {}", toolName);
                }
            }
        } catch (Exception e) {
            log.error("Failed to process streaming tool result", e);
        }
    }

    /**
     * Process tool results from AI analysis.
     */
    private void processToolResults(String sessionId, TarotSession session, List<AITool.ToolResult> toolResults) {
        for (AITool.ToolResult toolResult : toolResults) {
            if (toolResult.isSuccess()) {
                try {
                    // Get tool result data directly
                    Map<String, Object> toolData = toolResult.getData();
                    String toolName = toolResult.getMessage(); // Tool name is stored in message

                    // Extract actual tool name from the message (format: "toolname executed successfully")
                    if (toolName != null && toolName.contains(" ")) {
                        String[] parts = toolName.split(" ");
                        if (parts.length > 0) {
                            toolName = parts[0]; // Get first part which should be the tool name
                        }
                    }

                    log.info("Processing tool call: {} with data: {}", toolName, toolData);

                    // Handle specific tool calls with state transitions
                    switch (toolName) {
                        case "start_interpretation" -> {
                            // Transition to READY_FOR_INTERPRETATION state
                            updateSessionStatus(session, SessionStatus.READY_FOR_INTERPRETATION);

                            // Enable "Begin Interpretation" button
                            Map<String, Object> actionData = new HashMap<>();
                            actionData.put("userContext", toolData.get("userContext"));
                            actionData.put("overallTheme", toolData.get("overallTheme"));
                            actionData.put("readyToInterpret", toolData.get("readyToInterpret"));

                            SseEvent event = new SseEvent(sessionId, SseEventType.AI_INTERPRETATION_READY, actionData);
                            sseService.sendEvent(sessionId, event);
                            log.debug("Tool call processed: start_interpretation, transitioned to READY_FOR_INTERPRETATION");
                        }
                        case "reveal_card" -> {
                            String cardPosition = (String) toolData.get("cardPosition");
                            String interpretation = (String) toolData.get("interpretation");

                            // Reveal specific card for interpretation - include actual card data
                            Map<String, Object> cardDataMap = new HashMap<>();
                            cardDataMap.put("cardPosition", toolData.get("cardPosition"));
                            cardDataMap.put("cardInterpretationNote", toolData.get("cardInterpretationNote"));

                            // Get actual card data from session
                            List<Map<String, Object>> storedCards = parseStoredCards(session.getCards());
                            Map<String, Object> actualCard = getCardByPosition(storedCards, cardPosition);

                            if (actualCard != null) {
                                cardDataMap.put("cardName", actualCard.get("name"));
                                cardDataMap.put("arcanaType", actualCard.get("arcanaType"));
                                cardDataMap.put("cardNumber", actualCard.get("cardNumber"));
                                cardDataMap.put("isReversed", actualCard.get("isReversed"));
                            } else {
                                log.warn("Could not find card data for position: {}", cardPosition);
                            }

                            SseEvent event = new SseEvent(sessionId, SseEventType.AI_CARD_REVEAL, cardDataMap);
                            sseService.sendEvent(sessionId, event);

                            // Store the interpretation immediately from the tool call
                            if (interpretation != null && !interpretation.trim().isEmpty()) {
                                updateCardInterpretation(session, cardPosition, interpretation.trim());
                                log.info("Stored interpretation from tool call for {} card in session {}", cardPosition, session.getId());

                                // Transition state after interpretation is stored
                                transitionAfterInterpretation(session, cardPosition);
                            } else {
                                log.warn("No interpretation provided in reveal_card tool call for position: {}", cardPosition);
                            }

                            log.debug("Tool call processed: reveal_card for position: {} with interpretation", cardPosition);
                        }
                        case "complete_reading" -> {
                            // Complete the reading with final summary
                            Map<String, Object> completionData = new HashMap<>();
                            completionData.put("readingSummary", toolData.get("readingSummary"));
                            completionData.put("empoweringConclusion", toolData.get("empoweringConclusion"));
                            completionData.put("followUpReady", toolData.get("followUpReady"));

                            // Save the reading summary to the database
                            Optional<TarotSession> sessionOpt = tarotSessionRepository.findById(Long.valueOf(sessionId));

                            // Send pseudo message to trigger AI to stream closing message naturally
                            if (sessionOpt.isPresent()) {
                                triggerCompletionStreaming(sessionId, sessionOpt.get());
                            }

                            if (sessionOpt.isPresent()) {
                                TarotSession currentSession = sessionOpt.get();
                                String readingSummary = (String) toolData.get("readingSummary");
                                if (readingSummary != null && !readingSummary.trim().isEmpty()) {
                                    currentSession.setSummary(readingSummary.trim());
                                    currentSession.setStatus(SessionStatus.COMPLETED);
                                    currentSession.setCompletedAt(Instant.now());
                                    tarotSessionRepository.save(currentSession);
                                    log.debug("Reading summary saved to database for session: {}", sessionId);
                                }
                            } else {
                                log.warn("Session not found when trying to save reading summary: {}", sessionId);
                            }

                            SseEvent event = new SseEvent(sessionId, SseEventType.AI_READING_COMPLETE, completionData);
                            sseService.sendEvent(sessionId, event);
                            log.debug("Tool call processed: complete_reading");
                        }
                        default -> {
                            log.warn("Unknown tool call: {}", toolName);
                        }
                    }
                } catch (Exception e) {
                    log.error("Failed to process tool call result", e);
                }
            } else {
                log.warn("Tool call failed: {}", toolResult.getMessage());
            }
        }
    }

    /**
     * Parse stored cards JSON back into a usable format.
     */
    private List<Map<String, Object>> parseStoredCards(String cardsJson) {
        try {
            if (cardsJson == null || cardsJson.trim().isEmpty()) {
                return List.of();
            }
            TypeReference<List<Map<String, Object>>> typeRef = new TypeReference<List<Map<String, Object>>>() {};
            return objectMapper.readValue(cardsJson, typeRef);
        } catch (Exception e) {
            log.error("Failed to parse stored cards JSON", e);
            return List.of();
        }
    }

    /**
     * Parse stored interpretations JSON back into a usable format.
     * Interpretations are stored separately from cards with structure: [{position, name, interpretation}, ...]
     */
    private List<Map<String, Object>> parseStoredInterpretations(String interpretationsJson) {
        try {
            if (interpretationsJson == null || interpretationsJson.trim().isEmpty()) {
                return new ArrayList<>(); // Return mutable list for adding new interpretations
            }
            TypeReference<List<Map<String, Object>>> typeRef = new TypeReference<List<Map<String, Object>>>() {};
            List<Map<String, Object>> interpretations = objectMapper.readValue(interpretationsJson, typeRef);
            return new ArrayList<>(interpretations); // Ensure mutable list
        } catch (Exception e) {
            log.error("Failed to parse stored interpretations JSON", e);
            return new ArrayList<>(); // Return mutable empty list
        }
    }

    /**
     * Update a specific card's interpretation in the separate interpretations JSON.
     * Cards JSON remains clean with just card structure data.
     * Interpretations are stored separately in the interpretation field.
     */
    private void updateCardInterpretation(TarotSession session, String cardPosition, String interpretation) {
        try {
            // Parse existing interpretations or create empty list
            List<Map<String, Object>> interpretations = parseStoredInterpretations(session.getInterpretation());

            // Find and update the interpretation for this card position
            boolean found = false;
            for (Map<String, Object> interp : interpretations) {
                if (cardPosition.equals(interp.get("position"))) {
                    interp.put("interpretation", interpretation);
                    found = true;
                    log.debug("Updated interpretation for {} card", cardPosition);
                    break;
                }
            }

            // If not found, add new interpretation entry
            if (!found) {
                // Get card name from cards JSON for the interpretation entry
                List<Map<String, Object>> cards = parseStoredCards(session.getCards());
                String cardName = null;
                for (Map<String, Object> card : cards) {
                    if (cardPosition.equals(card.get("position"))) {
                        cardName = (String) card.get("name");
                        break;
                    }
                }

                if (cardName != null) {
                    Map<String, Object> newInterpretation = new HashMap<>();
                    newInterpretation.put("position", cardPosition);
                    newInterpretation.put("name", cardName);
                    newInterpretation.put("interpretation", interpretation);
                    interpretations.add(newInterpretation);
                    log.debug("Added new interpretation for {} card", cardPosition);
                }
            }

            // Convert interpretations back to JSON and save to interpretation field
            String updatedInterpretationsJson = objectMapper.writeValueAsString(interpretations);
            session.setInterpretation(updatedInterpretationsJson);
            tarotSessionRepository.save(session);

            log.info("Saved interpretation for {} card in session {} (separate JSON structure)", cardPosition, session.getId());

        } catch (Exception e) {
            log.error("Failed to update card interpretation for position: {}", cardPosition, e);
        }
    }

    /**
     * Get card info by position from stored cards.
     */
    private Map<String, Object> getCardByPosition(List<Map<String, Object>> storedCards, String position) {
        return storedCards.stream()
            .filter(card -> position.equals(card.get("position")))
            .findFirst()
            .orElse(null);
    }

    /**
     * Begin the interpretation of a tarot reading.
     * This method is called when the user clicks "Begin Interpretation" button.
     * It simply sends a chat message to trigger the natural AI flow.
     */
    public void beginInterpretation(Long sessionId) {
        log.debug("Beginning interpretation for session: {}", sessionId);

        // Verify session exists
        Optional<TarotSession> sessionOpt = tarotSessionRepository.findById(sessionId);
        if (sessionOpt.isEmpty()) {
            throw new IllegalArgumentException("Session not found: " + sessionId);
        }

        TarotSession session = sessionOpt.get();

        // Verify session has cards
        if (session.getCards() == null || session.getCards().isEmpty()) {
            throw new IllegalStateException("No cards found in session: " + sessionId);
        }

        // Transition to AWAITING_SITUATION state
        updateSessionStatus(session, SessionStatus.AWAITING_SITUATION);

        // Save a user message to trigger the natural AI flow
        String triggerMessage = "I'm ready to begin my reading interpretation.";

        ChatMessage userMessage = new ChatMessage();
        userMessage.setSession(session);
        userMessage.setSender(MessageSender.USER);
        userMessage.setMessage(triggerMessage);
        userMessage.setTimestamp(Instant.now());
        chatMessageRepository.save(userMessage);

        // Process AI response naturally - this will handle tool calls and interpretation together
        processAiResponse(session, triggerMessage);
    }

    /**
     * Complete the tarot reading.
     * This method is called when the user clicks "Complete Reading" button.
     * It sends a message to trigger the complete_reading tool call.
     */
    public void completeReading(Long sessionId) {
        log.debug("Completing reading for session: {}", sessionId);

        // Verify session exists
        Optional<TarotSession> sessionOpt = tarotSessionRepository.findById(sessionId);
        if (sessionOpt.isEmpty()) {
            throw new IllegalArgumentException("Session not found: " + sessionId);
        }

        TarotSession session = sessionOpt.get();

        // Verify session has cards
        if (session.getCards() == null || session.getCards().isEmpty()) {
            throw new IllegalStateException("No cards found in session: " + sessionId);
        }

        // Save a user message to trigger the complete reading tool call
        String triggerMessage = "Please complete my reading and provide the final summary.";

        ChatMessage userMessage = new ChatMessage();
        userMessage.setSession(session);
        userMessage.setSender(MessageSender.USER);
        userMessage.setMessage(triggerMessage);
        userMessage.setTimestamp(Instant.now());
        chatMessageRepository.save(userMessage);

        // Process AI response - this will trigger the complete_reading tool call
        processAiResponse(session, triggerMessage);
    }



    /**
     * Transition session state after interpretation is stored.
     * This ensures state transitions happen AFTER the work is completed.
     */
    private void transitionAfterInterpretation(TarotSession session, String cardPosition) {
        try {
            SessionStatus newStatus = switch (cardPosition.toLowerCase()) {
                case "situation" -> SessionStatus.AWAITING_OBSTACLE;
                case "obstacle" -> SessionStatus.AWAITING_ADVICE;
                case "advice" -> SessionStatus.READING_COMPLETE;
                default -> session.getStatus(); // No change for unknown positions
            };

            if (!newStatus.equals(session.getStatus())) {
                updateSessionStatus(session, newStatus);
                log.info("Session {} transitioned to {} after {} interpretation completed",
                        session.getId(), newStatus, cardPosition);
            }
        } catch (Exception e) {
            log.error("Error transitioning session state after {} interpretation for session: {}",
                     cardPosition, session.getId(), e);
        }
    }


    /**
     * Trigger AI to stream the interpretation naturally after a card reveal.
     * This sends a pseudo message to make the AI provide the interpretation with follow-up questions.
     */
    private void triggerInterpretationStreaming(String sessionId, TarotSession session, String cardPosition) {
        log.info("Triggering interpretation streaming for {} card in session {}", cardPosition, sessionId);

        // Create a pseudo message that will trigger the AI to stream the interpretation
        String pseudoMessage = "The " + cardPosition + " card has been revealed. Please share its interpretation with me.";

        // Process this as if it's a user message to get natural AI streaming response
        // This will use the streaming prompt version (no tools) to focus on interpretation delivery
        processAiResponse(session, pseudoMessage, false);
    }

    /**
     * Trigger AI to stream a natural closing message after reading completion.
     * This sends a pseudo message to make the AI provide a warm closing.
     */
    private void triggerCompletionStreaming(String sessionId, TarotSession session) {
        // Check if session is already completed to prevent infinite loop
//        if(session.getStatus().equals(SessionStatus.COMPLETED)) {
//            log.info("Session {} already completed, skipping completion streaming", sessionId);
//            return;
//        }
        log.info("Triggering completion streaming for session {}", sessionId);

        // Create a pseudo message that will trigger the AI to stream a closing message
        String pseudoMessage = "The reading has been completed. Please share any final thoughts and thank the user for their time.";

        // Process this as if it's a user message to get natural AI streaming response
        // This will use the streaming prompt version (no tools) to provide a warm closing
        processAiResponse(session, pseudoMessage, false);
    }

    /**
     * Helper method to update session status and save to database.
     */
    private void updateSessionStatus(TarotSession session, SessionStatus newStatus) {
        SessionStatus oldStatus = session.getStatus();
        session.setStatus(newStatus);
        tarotSessionRepository.save(session);
        log.debug("Session {} status updated from {} to {}", session.getId(), oldStatus, newStatus);
    }

    /**
     * Smart complete reading - checks for missing interpretations and fills them before completion.
     * This is the new workflow that ensures all card interpretations are present.
     */
    public void smartCompleteReading(Long sessionId) {
        log.debug("Smart completing reading for session: {}", sessionId);

        // Verify session exists
        Optional<TarotSession> sessionOpt = tarotSessionRepository.findById(sessionId);
        if (sessionOpt.isEmpty()) {
            throw new IllegalArgumentException("Session not found: " + sessionId);
        }

        TarotSession session = sessionOpt.get();

        // Verify session has cards
        if (session.getCards() == null || session.getCards().isEmpty()) {
            throw new IllegalStateException("No cards found in session: " + sessionId);
        }

        // Parse cards and interpretations
        List<Map<String, Object>> storedCards = parseStoredCards(session.getCards());
        List<Map<String, Object>> interpretations = parseStoredInterpretations(session.getInterpretation());

        // Check for missing interpretations
        String[] positions = {"situation", "obstacle", "advice"};
        List<String> missingPositions = new ArrayList<>();

        for (String position : positions) {
            boolean hasInterpretation = interpretations.stream()
                .anyMatch(interp -> position.equals(interp.get("position")));

            if (!hasInterpretation) {
                missingPositions.add(position);
                log.debug("Missing interpretation for position: {}", position);
            }
        }

        if (!missingPositions.isEmpty()) {
            log.info("Found {} missing interpretations for session {}: {}",
                missingPositions.size(), sessionId, missingPositions);

            // Get card details for missing positions and generate interpretations synchronously
            for (String missingPosition : missingPositions) {
                generateMissingInterpretation(session, storedCards, missingPosition);
            }
        } else {
            log.info("All interpretations present for session {}, proceeding directly to completion", sessionId);
        }

        // Now all interpretations are filled, no need to trigger completed (its already been change to complete status)
    }

    /**
     * Generate missing interpretation for a card position without triggering SSE events.
     * Uses standalone prompt approach to avoid tool call dependency.
     */
    private void generateMissingInterpretation(TarotSession session, List<Map<String, Object>> storedCards, String position) {
        log.debug("Generating missing interpretation for position: {} in session: {}", position, session.getId());

        // Find the card for this position
        Map<String, Object> card = getCardByPosition(storedCards, position);
        if (card == null) {
            log.error("No card found for position: {} in session: {}", position, session.getId());
            return;
        }

        String cardName = (String) card.get("name");
        boolean isReversed = Boolean.TRUE.equals(card.get("isReversed"));

        // Get conversation history for better context
        List<String> conversationHistory = getConversationHistory(session.getId());

        // Build card data for context
        List<TarotCardData> allCards = storedCards.stream()
            .map(c -> new TarotCardData(
                (String) c.get("name"),
                (String) c.get("position"),
                Boolean.TRUE.equals(c.get("isReversed"))
            ))
            .collect(Collectors.toList());

        try {
            // Use standalone prompt that doesn't rely on tools
            String prompt = tarotPrompts.getMissingInterpretationPrompt(position, cardName, isReversed, allCards, conversationHistory);

            log.info("Generating interpretation for {} card using standalone prompt with {} conversation history entries",
                position, conversationHistory.size());

            // Create a simple request to Gemini without tools
            String response = geminiService.generateSimpleResponse(prompt);

            if (response != null && !response.trim().isEmpty()) {
                // Parse XML response to extract interpretation
                String interpretation = extractInterpretationFromXML(response);

                if (interpretation != null && !interpretation.trim().isEmpty()) {
                    // Store interpretation without sending SSE events
                    updateCardInterpretation(session, position, interpretation.trim());
                    log.info("Successfully generated and saved interpretation for {} card using standalone prompt", position);
                } else {
                    log.error("Failed to extract interpretation from XML response for {} card", position);
                    log.debug("XML response: {}", response);
                }
            } else {
                log.error("Failed to generate interpretation for {} card - empty response", position);
            }

        } catch (Exception e) {
            log.error("Error generating interpretation for {} card", position, e);
        }
    }

    /**
     * Extract interpretation text from XML response.
     * Looks for content between <interpretation> tags.
     */
    private String extractInterpretationFromXML(String xmlResponse) {
        try {
            // Simple regex to extract content between <interpretation> tags
            // Using (?s) to make . match newlines
            String pattern = "(?s)<interpretation>\\s*(.*?)\\s*</interpretation>";
            java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern);
            java.util.regex.Matcher m = p.matcher(xmlResponse);

            if (m.find()) {
                String interpretation = m.group(1).trim();
                log.debug("Successfully extracted interpretation from XML, length: {}", interpretation.length());
                return interpretation;
            } else {
                log.warn("No <interpretation> tags found in XML response");
                return null;
            }
        } catch (Exception e) {
            log.error("Error parsing XML response", e);
            return null;
        }
    }

}
