package dev.sonpx.loyalty.agent.core.workflow;

import dev.sonpx.loyalty.agent.core.classifier.Intent;
import dev.sonpx.loyalty.agent.core.executor.AgentToolScope;
import dev.sonpx.loyalty.agent.core.executor.ToolInterceptor;
import dev.sonpx.loyalty.agent.core.memory.AgentMemoryManager;
import dev.sonpx.loyalty.agent.domain.ChatRequest;
import dev.sonpx.loyalty.agent.service.AgentEventPublisher;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * Manages multi-turn conversation workflows for creating campaigns and rules.
 *
 * Flow:
 * 1. User says "tạo chiến dịch" → WorkflowExecutor starts workflow, asks for info
 * 2. User provides info → LLM extracts data, asks for more or confirms
 * 3. User confirms → LLM calls createCampaign/createRule tool
 *
 * Key design decisions:
 * - WorkflowState is stored per-session (in-memory ConcurrentHashMap)
 * - LLM is used to extract data from natural language and generate responses
 * - Tool calls only happen in the final EXECUTE phase
 * - User can cancel at any time
 */
@Slf4j
@Component
public class WorkflowExecutor {

    private static final Pattern SIDE_QUERY_PATTERN = Pattern.compile(
            "(?i)^\\s*(xem|tìm|danh sách|liệt kê|chi tiết|có bao nhiêu|đếm|show|list)\\b.*");

    private final ChatClient chatClient;
    private final ToolInterceptor toolInterceptor;
    private final AgentEventPublisher eventPublisher;
    private final AgentMemoryManager memoryManager;
    private final WorkflowDataExtractor dataExtractor;
    private final dev.sonpx.loyalty.agent.service.AgentPersistenceService persistenceService;

    /** Active workflows per session */
    private final Map<String, WorkflowState> activeWorkflows = new ConcurrentHashMap<>();

    public WorkflowExecutor(@Qualifier("creatorAgentChatClient") ChatClient chatClient,
                            ToolInterceptor toolInterceptor,
                            AgentEventPublisher eventPublisher,
                            AgentMemoryManager memoryManager,
                            WorkflowDataExtractor dataExtractor,
                            dev.sonpx.loyalty.agent.service.AgentPersistenceService persistenceService) {
        this.chatClient = chatClient;
        this.toolInterceptor = toolInterceptor;
        this.eventPublisher = eventPublisher;
        this.memoryManager = memoryManager;
        this.dataExtractor = dataExtractor;
        this.persistenceService = persistenceService;
    }

    /**
     * Check if there's an active workflow for this session.
     */
    public boolean hasActiveWorkflow(String sessionId) {
        WorkflowState state = activeWorkflows.get(sessionId);
        return state != null && state.isActive();
    }

    /**
     * Get the active workflow for a session.
     */
    public WorkflowState getWorkflow(String sessionId) {
        return activeWorkflows.get(sessionId);
    }

    /**
     * Allow explicit read/query turns while preserving the active create workflow.
     */
    public boolean isSideQuery(String userInput) {
        return userInput != null && SIDE_QUERY_PATTERN.matcher(userInput).find();
    }

    /**
     * Start a new workflow for creating a campaign or rule.
     * Sends the initial question to the user.
     */
    /**
     * Start a new workflow for creating a campaign or rule.
     * Sends the initial question to the user.
     */
    public void startWorkflow(Intent intent, ChatRequest request, String connectionSessionId, String conversationKey) {
        WorkflowState state = new WorkflowState(intent);
        activeWorkflows.put(conversationKey, state);

        log.info("Starting {} workflow for conversation: {}, connection: {}", intent, conversationKey, connectionSessionId);

        // Build the initial prompt based on workflow type
        String systemPrompt = getPhasePrompt(state);
        String userPrompt = request.prompt() != null ? request.prompt() : "Bắt đầu tạo";

        streamResponse(systemPrompt, userPrompt, connectionSessionId, conversationKey, request.messageId(), false);
    }

    /**
     * Continue an active workflow with the user's response.
     * Routes to the appropriate handler based on current phase.
     */
    public void continueWorkflow(ChatRequest request, String connectionSessionId, String conversationKey) {
        WorkflowState state = activeWorkflows.get(conversationKey);
        if (state == null || !state.isActive()) {
            log.warn("No active workflow for conversation: {}", conversationKey);
            return;
        }

        if (state.isExecuting()) {
            streamResponse(
                    "Việc tạo đang được thực hiện. Hãy nói ngắn gọn rằng hệ thống đang xử lý và người dùng vui lòng chờ kết quả.",
                    request.prompt(), connectionSessionId, conversationKey, request.messageId(), false);
            return;
        }

        String userInput = request.prompt();

        // Check for cancellation at any phase
        if (WorkflowPromptBuilder.isCancellation(userInput)) {
            state.cancel();
            activeWorkflows.remove(conversationKey);
            streamResponse(
                    "Người dùng đã hủy. Hãy nói: 'Đã hủy. Nếu cần giúp gì khác, hãy cho tôi biết.'",
                    userInput, connectionSessionId, conversationKey, request.messageId(), false);
            return;
        }

        log.info("Continuing workflow for conversation: {}, phase: {}", conversationKey, state.getCurrentPhase());

        switch (state.getCurrentPhase()) {
            case BASIC_INFO -> handleBasicInfoResponse(state, request, connectionSessionId, conversationKey);
            case REWARD_CONFIG -> handleRewardConfigResponse(state, request, connectionSessionId, conversationKey);
            case CONFIRMING -> handleConfirmation(state, request, connectionSessionId, conversationKey);
            default -> log.warn("Unexpected workflow phase: {}", state.getCurrentPhase());
        }
    }

    /**
     * Handle user's response during BASIC_INFO phase.
     * Use LLM to extract structured data from natural language.
     */
    private void handleBasicInfoResponse(WorkflowState state, ChatRequest request, String connectionSessionId, String conversationKey) {
        state.putAllData(dataExtractor.extract(state, request.prompt()));

        if (!dataExtractor.hasEnoughForNextPhase(state, request.prompt())) {
            streamResponse(WorkflowPromptBuilder.missingInfoPrompt(state),
                    request.prompt(), connectionSessionId, conversationKey, request.messageId(), false);
            return;
        }

        String systemPrompt = WorkflowPromptBuilder.extractDataPrompt(state);

        // LLM extracts data and confirms — no tools needed yet
        streamResponse(systemPrompt, request.prompt(), connectionSessionId, conversationKey, request.messageId(), false);

        // Advance phase after LLM responds
        // The LLM will indicate if data is complete; user's next message drives phase transition
        state.advancePhase();
    }

    /**
     * Handle user's response during REWARD_CONFIG phase (rule only).
     */
    private void handleRewardConfigResponse(WorkflowState state, ChatRequest request, String connectionSessionId, String conversationKey) {
        // Check if user wants to skip optional config
        if (WorkflowPromptBuilder.isSkipToConfirm(request.prompt())) {
            state.advancePhase(); // Skip to CONFIRMING
            String confirmPrompt = getPhasePrompt(state);
            streamResponse(confirmPrompt, request.prompt(), connectionSessionId, conversationKey, request.messageId(), false);
            return;
        }

        state.putAllData(dataExtractor.extract(state, request.prompt()));

        if (!dataExtractor.hasEnoughForNextPhase(state, request.prompt())) {
            streamResponse(WorkflowPromptBuilder.missingInfoPrompt(state),
                    request.prompt(), connectionSessionId, conversationKey, request.messageId(), false);
            return;
        }

        String systemPrompt = WorkflowPromptBuilder.extractDataPrompt(state);
        streamResponse(systemPrompt, request.prompt(), connectionSessionId, conversationKey, request.messageId(), false);

        // Advance to CONFIRMING
        state.advancePhase();
    }

    /**
     * Handle user's confirmation or rejection.
     */
    private void handleConfirmation(WorkflowState state, ChatRequest request, String connectionSessionId, String conversationKey) {
        if (WorkflowPromptBuilder.isConfirmation(request.prompt())) {
            // User confirmed → execute creation with tools
            log.info("User confirmed creation for conversation: {}", conversationKey);
            state.startExecuting();

            String executePrompt = (state.getWorkflowType() == Intent.CAMPAIGN_CREATE)
                    ? WorkflowPromptBuilder.campaignExecutePrompt(state.getCollectedData())
                    : WorkflowPromptBuilder.ruleExecutePrompt(state.getCollectedData());

            // This time we need tools to actually create the entity
            AgentToolScope toolScope = (state.getWorkflowType() == Intent.CAMPAIGN_CREATE)
                    ? AgentToolScope.CREATE_CAMPAIGN
                    : AgentToolScope.CREATE_RULE;
            streamResponse(executePrompt, request.prompt(), connectionSessionId, conversationKey, request.messageId(), true, toolScope,
                    () -> {
                        state.complete();
                        activeWorkflows.remove(conversationKey);
                    },
                    state::failExecution);
        } else {
            // User wants to modify — go back to collect more info
            state.putAllData(dataExtractor.extract(state, request.prompt()));
            String systemPrompt = WorkflowPromptBuilder.extractDataPrompt(state);
            streamResponse(systemPrompt, request.prompt(), connectionSessionId, conversationKey, request.messageId(), false);
        }
    }

    /**
     * Get the appropriate prompt for the current workflow phase.
     */
    private String getPhasePrompt(WorkflowState state) {
        boolean isCampaign = state.getWorkflowType() == Intent.CAMPAIGN_CREATE;

        return switch (state.getCurrentPhase()) {
            case BASIC_INFO -> isCampaign
                    ? WorkflowPromptBuilder.campaignBasicInfoPrompt()
                    : WorkflowPromptBuilder.ruleBasicInfoPrompt();
            case REWARD_CONFIG -> WorkflowPromptBuilder.ruleRewardConfigPrompt(state.getCollectedData());
            case CONFIRMING -> isCampaign
                    ? WorkflowPromptBuilder.campaignConfirmPrompt(state.getCollectedData())
                    : WorkflowPromptBuilder.ruleConfirmPrompt(state.getCollectedData());
            default -> "Hãy giúp người dùng.";
        };
    }

    /**
     * Stream a response to the user via WebSocket.
     *
     * @param withTools if true, loads MCP tools for the LLM to call
     */
    private void streamResponse(String systemPrompt, String userPrompt,
                                String connectionSessionId, String conversationKey,
                                String messageId, boolean withTools) {
        streamResponse(systemPrompt, userPrompt, connectionSessionId, conversationKey, messageId, withTools, AgentToolScope.ALL, null, null);
    }

    private void streamResponse(String systemPrompt, String userPrompt,
                                String connectionSessionId, String conversationKey,
                                String messageId, boolean withTools,
                                AgentToolScope toolScope, Runnable onComplete, Runnable onError) {
        persistenceService.saveUserMessage(conversationKey, messageId, userPrompt);
        StringBuilder responseAccumulator = new StringBuilder();

        var builder = chatClient.prompt()
                .advisors(memoryManager.getAdvisor())
                .advisors(a -> a
                        .param("chat_memory_conversation_id", conversationKey)
                        .param("chat_memory_response_size", 50))
                .system(systemPrompt)
                .user(userPrompt != null ? userPrompt : "Tiếp tục");

        if (withTools) {
            List<ToolCallback> wrappedTools = toolInterceptor.getWrappedTools(connectionSessionId, messageId, toolScope);
            builder.tools(wrappedTools);
        }

        builder.stream()
                .content()
                .doOnComplete(() -> {
                    persistenceService.saveAssistantMessage(conversationKey, responseAccumulator.toString());
                    if (onComplete != null) {
                        onComplete.run();
                    }
                    eventPublisher.done(connectionSessionId, messageId);
                })
                .subscribe(
                        content -> {
                            if (!content.isEmpty()) {
                                responseAccumulator.append(content);
                                eventPublisher.token(connectionSessionId, messageId, content);
                            }
                        },
                        error -> {
                            log.error("Error during workflow execution: {}", error.getMessage(), error);
                            if (onError != null) {
                                onError.run();
                            }
                            eventPublisher.error(connectionSessionId, messageId,
                                    "Xin lỗi, đã xảy ra lỗi. Vui lòng thử lại.");
                        }
                );
    }
}
