import { IStorage } from "./storage";
import { User, InsertTask, insertTaskSchema } from "../shared/schema";

interface ChatMessage {
    role: "system" | "user" | "assistant";
    content: string;
}

export class AiService {
    constructor(private storage: IStorage) { }

    async chat(messages: ChatMessage[], user: User, context: string): Promise<string> {
        const provider = await this.storage.getSystemSettings("ai_provider") || "openai";
        const apiKey = await this.storage.getSystemSettings("ai_api_key");
        const model = await this.storage.getSystemSettings("ai_model") || "gpt-4o";
        const baseUrl = await this.storage.getSystemSettings("ai_base_url");

        if (!apiKey && provider !== "ollama") {
            throw new Error("AI API Key not configured");
        }

        const systemPrompt = `You are TaskFlow AI, an intelligent assistant for the TaskFlow application.
You have access to the user's current tasks and context.
User Name: ${user.username}
Current Context:
${context}

Answer the user's questions based on this context. Be concise, helpful, and friendly.

### 🛠️ AVAILABLE TOOLS
You can create, search, update, and delete tasks using the provided tools.

**1. Task Management**
- **Create**: Use 'createTask'. Title is required.
    - *Subtasks*: To break a task down, create new tasks with 'parentTaskId' set to the main task's ID.
    - *Planning*: You can set 'estimatedDuration' (minutes) and 'startDate'.
- **Update/Delete**: First SEARCH for the task ID using 'searchTasks', then use 'updateTask' or 'deleteTask'.
- **Labels**: Use 'getLabels' to see available tags.

**2. 🧠 Smart Planning & Scheduling**
- **"Break this down"**: If a user asks to break down a project, create strictly hierarchical subtasks using 'createTask' with 'parentTaskId'.
- **"Find time for this"**: Use 'scheduleTask'. This tool automatically finds free slots in the user's calendar (based on task duration) and sets the due date.
- **Time Boxing**: If a user mentions how long something takes ("...will take 2 hours"), ALWAYS set 'estimatedDuration' (in minutes) when creating/updating.

**3. Gamification**
- Check achievements/XP with 'getAchievements'.
- Check highscores with 'getLeaderboard'.

### 📅 DATE & TIME RULES
- **"Today"**: Use the 'Current Date/Time' context.
- **Queries**: When asked for "today's tasks", only list tasks where 'dueDate' matches today.
- **Scheduling**: When using 'scheduleTask', inform the user specifically *when* you scheduled it (e.g., "I've scheduled this for tomorrow at 2:00 PM").
 
IMPORTANT: Do NOT show Task IDs to the user. Reference tasks by Title.
CRITICAL: After executing tools, provide a concise summary of your actions.
`;

        const fullMessages = [
            { role: "system", content: systemPrompt },
            ...messages
        ];

        try {
            if (provider === "openai" || provider === "ollama") {
                return await this.chatOpenAI(provider, apiKey || "", model, baseUrl, fullMessages, user);
            } else if (provider === "anthropic") {
                return await this.chatAnthropic(apiKey || "", model, fullMessages);
            } else if (provider === "google") {
                return await this.chatGemini(apiKey || "", model, fullMessages);
            } else {
                throw new Error(`Unsupported AI provider: ${provider}`);
            }
        } catch (error: any) {
            console.error("AI Chat Error:", error);
            throw new Error(`AI Service Error: ${error.message}`);
        }
    }

    async generateTitle(messages: ChatMessage[]): Promise<string> {
        const provider = await this.storage.getSystemSettings("ai_provider") || "openai";
        const apiKey = await this.storage.getSystemSettings("ai_api_key");
        const model = await this.storage.getSystemSettings("ai_model") || "gpt-4o";
        const baseUrl = await this.storage.getSystemSettings("ai_base_url");

        if (!apiKey && provider !== "ollama") return "New Conversation";

        const conversationText = messages.slice(0, 3).map(m => `${m.role}: ${m.content}`).join('\n');
        const prompt = `Based on the following conversation, generate a short, concise title (max 5 words). Return ONLY the title text, no quotes or labels.\n\nConversation:\n${conversationText}`;

        const contextMessages = [
            { role: "user", content: prompt }
        ];

        try {
            let generatedTitle = "";
            if (provider === "openai" || provider === "ollama") {
                generatedTitle = await this.chatOpenAI(provider, apiKey || "", model, baseUrl, contextMessages, undefined, []);
            } else if (provider === "anthropic") {
                generatedTitle = await this.chatAnthropic(apiKey || "", model, contextMessages);
            } else if (provider === "google") {
                generatedTitle = await this.chatGemini(apiKey || "", model, contextMessages);
            }

            generatedTitle = generatedTitle.trim();
            if (generatedTitle && generatedTitle !== "No response generated." && generatedTitle.length < 60) {
                return generatedTitle.replace(/^["']|["']$/g, '').replace(/[*_#`]/g, '').trim();
            }
        } catch (error) {
            console.error("Title generation failed:", error);
        }
        return "New Conversation";
    }

    // New Public Method for Smart Scheduling
    async scheduleTask(taskId: string, userId: string, startAfterStr?: string): Promise<{ success: boolean, scheduledDate?: string, message?: string, error?: string }> {
        const task = await this.storage.getTask(taskId);
        if (!task) {
            return { success: false, error: "Task not found." };
        }

        const startAfter = startAfterStr ? new Date(startAfterStr) : new Date();
        const durationMins = task.estimatedDuration || 60; // Default to 1h if not set

        const workStartHour = 9;
        // PRIORITY LOGIC: High priority tasks can be scheduled until 20:00 (8 PM)
        const workEndHour = task.priority === 'high' ? 20 : 18;

        let scheduledDate: Date | null = null;

        // PLANNED TIME LOGIC: If startDate is set, do not schedule before it.
        // If starteAfterStr is provided (e.g. "tomorrow"), use the max of both.
        let effectiveStart = startAfter;
        if (task.startDate) {
            const plannedStart = new Date(task.startDate);
            if (plannedStart > effectiveStart) {
                effectiveStart = plannedStart;
            }
        }

        let currentDay = new Date(effectiveStart);

        // Reset to next slot if passed
        if (currentDay.getHours() >= workEndHour) {
            currentDay.setDate(currentDay.getDate() + 1);
            currentDay.setHours(workStartHour, 0, 0, 0);
        } else if (currentDay.getHours() < workStartHour) {
            currentDay.setHours(workStartHour, 0, 0, 0);
        }

        for (let dayOffset = 0; dayOffset < 3; dayOffset++) { // Look ahead 3 days
            const dayStart = new Date(currentDay);
            dayStart.setHours(workStartHour, 0, 0, 0);
            const dayEnd = new Date(currentDay);
            dayEnd.setHours(workEndHour, 0, 0, 0);

            // Get all tasks for this day that have a due date (and time)
            const allTasks = await this.storage.searchTasks("", userId);

            // Filter for tasks on this day
            const dayTasks = allTasks.filter(t => {
                if (!t.dueDate) return false;
                const d = new Date(t.dueDate);
                return d.getDate() === currentDay.getDate() &&
                    d.getMonth() === currentDay.getMonth() &&
                    d.getFullYear() === currentDay.getFullYear();
            });

            // Find gaps
            // Sort by time
            dayTasks.sort((a, b) => (a.dueDate!.getTime() - b.dueDate!.getTime()));

            // Check slots
            // Start checking from 'currentDay' time (if today) or 9am
            let attemptTime = new Date(currentDay);
            if (attemptTime < dayStart) attemptTime = dayStart;

            while (attemptTime.getTime() + (durationMins * 60000) <= dayEnd.getTime()) {
                const attemptEnd = new Date(attemptTime.getTime() + (durationMins * 60000));

                // Check collision
                const hasCollision = dayTasks.some(t => {
                    const tStart = new Date(t.dueDate!);
                    const tDuration = t.estimatedDuration || 60;
                    const tEnd = new Date(tStart.getTime() + (tDuration * 60000));

                    return (attemptTime < tEnd && attemptEnd > tStart);
                });

                if (!hasCollision) {
                    scheduledDate = attemptTime;
                    break;
                }

                // specific increment? 30 mins
                attemptTime = new Date(attemptTime.getTime() + 30 * 60000);
            }

            if (scheduledDate) break;

            // Move to next day
            currentDay.setDate(currentDay.getDate() + 1);
            currentDay.setHours(workStartHour, 0, 0, 0);
        }

        if (scheduledDate) {
            await this.storage.updateTask(taskId, { dueDate: scheduledDate });
            return {
                success: true,
                scheduledDate: scheduledDate.toISOString(),
                message: `Scheduled for ${scheduledDate.toLocaleDateString()} at ${scheduledDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}.`
            };
        } else {
            return { success: false, error: "Could not find a free slot in the next 3 days." };
        }
    }

    private getTaskTools() {
        return [
            {
                type: "function",
                function: {
                    name: "createTask",
                    description: "Create a new task for the user.",
                    parameters: {
                        type: "object",
                        properties: {
                            title: { type: "string", description: "The title of the task (required)." },
                            description: { type: "string" },
                            priority: { type: "string", enum: ["low", "medium", "high"] },
                            status: { type: "string", enum: ["todo", "inProgress", "done"] },
                            dueDate: { type: "string", description: "ISO 8601 format (YYYY-MM-DD)." },
                            labelId: { type: "string" },
                            estimatedDuration: { type: "integer", description: "Estimated duration in minutes." },
                            parentTaskId: { type: "string" },
                            startDate: { type: "string" }
                        },
                        required: ["title"]
                    }
                }
            },
            {
                type: "function",
                function: {
                    name: "searchTasks",
                    description: "Search for tasks by title, description, or label.",
                    parameters: {
                        type: "object",
                        properties: {
                            query: { type: "string" },
                            label: { type: "string" }
                        },
                        required: ["query"]
                    }
                }
            },
            {
                type: "function",
                function: {
                    name: "updateTask",
                    description: "Update an existing task.",
                    parameters: {
                        type: "object",
                        properties: {
                            id: { type: "string" },
                            title: { type: "string" },
                            description: { type: "string" },
                            priority: { type: "string", enum: ["low", "medium", "high"] },
                            status: { type: "string", enum: ["todo", "inProgress", "done"] },
                            dueDate: { type: "string" },
                            timeTracked: { type: "number" },
                            labelId: { type: "string" },
                            estimatedDuration: { type: "integer" },
                            startDate: { type: "string" }
                        },
                        required: ["id"]
                    }
                }
            },
            {
                type: "function",
                function: {
                    name: "deleteTask",
                    description: "Delete an existing task.",
                    parameters: {
                        type: "object",
                        properties: { id: { type: "string" } },
                        required: ["id"]
                    }
                }
            },
            {
                type: "function",
                function: {
                    name: "getLabels",
                    description: "Get all available labels.",
                    parameters: { type: "object", properties: {}, required: [] }
                }
            },
            {
                type: "function",
                function: {
                    name: "getAchievements",
                    description: "Get user's XP and rewards.",
                    parameters: { type: "object", properties: {}, required: [] }
                }
            },
            {
                type: "function",
                function: {
                    name: "getLeaderboard",
                    description: "Get the highscore leaderboard.",
                    parameters: { type: "object", properties: {}, required: [] }
                }
            },
            {
                type: "function",
                function: {
                    name: "scheduleTask",
                    description: "Finds the first available time slot for a task based on its duration and existing schedule.",
                    parameters: {
                        type: "object",
                        properties: {
                            taskId: { type: "string", description: "The ID of the task to schedule." },
                            startAfter: { type: "string", description: "ISO 8601 Date to start searching from (default: now)." }
                        },
                        required: ["taskId"]
                    }
                }
            }
        ];
    }

    private async chatOpenAI(provider: string, apiKey: string, model: string, baseUrl: string | undefined, messages: any[], user?: User, tools: any[] = this.getTaskTools()): Promise<string> {
        let apiBase = baseUrl;

        if (!apiBase) {
            if (provider === "ollama") {
                apiBase = "http://host.docker.internal:11434/v1";
            } else {
                apiBase = "https://api.openai.com/v1";
            }
        }

        if (apiBase.endsWith('/')) {
            apiBase = apiBase.slice(0, -1);
        }

        if (provider === "ollama" && !apiBase.includes("/v1") && !apiBase.includes("/api")) {
            apiBase += "/v1";
        }

        const url = `${apiBase}/chat/completions`;
        console.log(`[AI] Sending Chat Request: provider=${provider} url=${url} model=${model}`);

        const body: any = {
            model: model,
            messages: messages,
            temperature: 0.7,
        };

        if (tools && tools.length > 0) {
            body.tools = tools;
            body.tool_choice = "auto";
        }

        try {
            const response = await fetch(url, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${apiKey}`,
                },
                body: JSON.stringify(body),
            });

            if (!response.ok) {
                const err = await response.text();
                console.error(`[AI] API Error: ${response.status} - ${err}`);
                throw new Error(`${provider} API Error ${response.status}: ${err}`);
            }

            const data = await response.json();
            const choice = data.choices[0];
            const message = choice.message;

            if (choice.finish_reason === "tool_calls" && message.tool_calls) {
                console.log(`[AI] Tool Calls detected: ${message.tool_calls.length}`);

                if (!user) throw new Error("Tools invoked but no user context provided.");

                messages.push(message);

                for (const toolCall of message.tool_calls) {
                    const fnName = toolCall.function.name;
                    const args = JSON.parse(toolCall.function.arguments);
                    let result: any = {};

                    try {
                        console.log(`[AI] Executing ${fnName}:`, args);

                        if (fnName === "createTask") {
                            const taskData = {
                                title: args.title,
                                description: args.description || null,
                                priority: args.priority || "medium",
                                status: args.status || "todo",
                                dueDate: args.dueDate ? new Date(args.dueDate) : null,
                                labelId: args.labelId || null,
                                estimatedDuration: args.estimatedDuration || null,
                                parentTaskId: args.parentTaskId || null,
                                startDate: args.startDate ? new Date(args.startDate) : null,
                                userId: user.id
                            };
                            const createdTask = await this.storage.createTask(taskData);
                            await this.storage.createAuditLog({
                                userId: user.id,
                                action: "CREATE",
                                entityType: "TASK",
                                entityId: createdTask.id,
                                details: { title: createdTask.title },
                                source: "AI"
                            });
                            result = { success: true, taskId: createdTask.id, message: "Task created." };
                        } else if (fnName === "searchTasks") {
                            const tasks = await this.storage.searchTasks(args.query, user.id);
                            const labels = await this.storage.getLabels(user.id);
                            const labelMap = new Map(labels.map(l => [l.id, l.name]));

                            let filteredTasks = tasks;
                            if (args.label) {
                                const labelId = labels.find(l => l.name.toLowerCase() === args.label.toLowerCase())?.id;
                                if (labelId) {
                                    filteredTasks = tasks.filter(t => t.labelId === labelId);
                                }
                            }

                            result = {
                                success: true,
                                tasks: filteredTasks.map(t => ({
                                    id: t.id,
                                    title: t.title,
                                    status: t.status,
                                    priority: t.priority,
                                    dueDate: t.dueDate,
                                    label: t.labelId ? labelMap.get(t.labelId) || "No Label" : "No Label"
                                }))
                            };
                        } else if (fnName === "updateTask") {
                            const updates: any = {};
                            if (args.title) updates.title = args.title;
                            if (args.description) updates.description = args.description;
                            if (args.priority) updates.priority = args.priority;
                            if (args.status) updates.status = args.status;
                            if (args.dueDate !== undefined) updates.dueDate = args.dueDate ? new Date(args.dueDate) : null;
                            if (args.timeTracked !== undefined) updates.timeTracked = args.timeTracked;
                            if (args.labelId !== undefined) updates.labelId = args.labelId;
                            if (args.estimatedDuration !== undefined) updates.estimatedDuration = args.estimatedDuration;
                            if (args.startDate !== undefined) updates.startDate = args.startDate ? new Date(args.startDate) : null;

                            const updatedTask = await this.storage.updateTask(args.id, updates);
                            if (updatedTask) {
                                await this.storage.createAuditLog({
                                    userId: user.id,
                                    action: "UPDATE",
                                    entityType: "TASK",
                                    entityId: updatedTask.id,
                                    details: updates,
                                    source: "AI"
                                });
                                result = { success: true, message: "Task updated successfully." };
                            } else {
                                result = { success: false, error: "Task not found." };
                            }
                        } else if (fnName === "deleteTask") {
                            const deleted = await this.storage.deleteTask(args.id);
                            if (deleted) {
                                await this.storage.createAuditLog({
                                    userId: user.id,
                                    action: "DELETE",
                                    entityType: "TASK",
                                    entityId: args.id,
                                    details: null,
                                    source: "AI"
                                });
                                result = { success: true, message: "Task deleted successfully." };
                            } else {
                                result = { success: false, error: "Task not found or could not be deleted." };
                            }
                        } else if (fnName === "getLabels") {
                            const labels = await this.storage.getLabels(user.id);
                            result = {
                                success: true,
                                labels: labels.map(l => ({ id: l.id, name: l.name, color: l.color }))
                            };
                        } else if (fnName === "getAchievements") {
                            const goals = await this.storage.getGoals();
                            const userGoals = goals.filter(g => g.userId === user.id);
                            const rewards = await this.storage.getUserRewards(user.id);
                            const xpEvents = await this.storage.getXpEvents(user.id);

                            result = {
                                success: true,
                                xp: user.xp,
                                level: user.level,
                                goals: userGoals.map(g => ({ title: g.title, current: g.current, target: g.target, completed: g.completed })),
                                rewards: rewards.length,
                                recentXp: xpEvents.slice(0, 5).map(e => ({ source: e.source, amount: e.amount }))
                            };
                        } else if (fnName === "getLeaderboard") {
                            const leaderboard = await this.storage.getLeaderboard();
                            result = {
                                success: true,
                                topUsers: leaderboard.slice(0, 10).map((u, index) => ({
                                    rank: index + 1,
                                    username: u.username,
                                    xp: u.xp,
                                    level: u.level
                                }))
                            };
                        } else if (fnName === "scheduleTask") {
                            // Call the reusable public method from inside the tool
                            result = await this.scheduleTask(args.taskId, user.id, args.startAfter);
                        } else {
                            result = { success: false, error: "Unknown tool function." };
                        }

                        messages.push({
                            role: "tool",
                            tool_call_id: toolCall.id,
                            name: fnName,
                            content: JSON.stringify(result)
                        });

                    } catch (err: any) {
                        console.error(`[AI] Tool Execution Error:`, err);
                        messages.push({
                            role: "tool",
                            tool_call_id: toolCall.id,
                            name: fnName,
                            content: JSON.stringify({ success: false, error: err.message })
                        });
                    }
                }

                console.log(`[AI] Sending follow-up request with tool results...`);
                return await this.chatOpenAI(provider, apiKey, model, baseUrl, messages, user, tools);
            }

            if (!message.content) {
                console.warn("[AI] Warning: Empty content received from OpenAI.");
                const hasExecutedTools = messages.some(m => m.role === 'tool');
                if (hasExecutedTools) {
                    return "I have successfully processed your request and updated your tasks.";
                }
            }
            return message.content || "No response generated.";

        } catch (error: any) {
            console.error(`[AI] Request Failed: ${error.message}`);
            if (error.code === 'ECONNREFUSED' && url.includes('localhost')) {
                throw new Error("Connection refused. If running in Docker, try using 'http://host.docker.internal:11434/v1' as Base URL.");
            }
            throw error;
        }
    }


    private async chatAnthropic(apiKey: string, model: string, messages: any[]): Promise<string> {
        const systemMessage = messages.find(m => m.role === "system")?.content || "";
        const userAssistantMessages = messages.filter(m => m.role !== "system");

        const response = await fetch("https://api.anthropic.com/v1/messages", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "x-api-key": apiKey,
                "anthropic-version": "2023-06-01",
            },
            body: JSON.stringify({
                model: model,
                system: systemMessage,
                messages: userAssistantMessages,
                max_tokens: 1024,
            }),
        });

        if (!response.ok) {
            const err = await response.text();
            throw new Error(`Anthropic API Error ${response.status}: ${err}`);
        }

        const data = await response.json();
        return data.content[0]?.text || "No response generated.";
    }

    private async chatGemini(apiKey: string, model: string, messages: any[]): Promise<string> {
        const systemMessage = messages.find(m => m.role === "system")?.content || "";
        const contentMessages = messages.filter(m => m.role !== "system").map(m => ({
            role: m.role === "user" ? "user" : "model",
            parts: [{ text: m.content }]
        }));

        if (contentMessages.length > 0 && contentMessages[0].role === "user") {
            contentMessages[0].parts[0].text = `[System Instruction: ${systemMessage}]\n\nWait for user input... User Input: ` + contentMessages[0].parts[0].text;
        }

        const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;

        const response = await fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                contents: contentMessages
            }),
        });

        if (!response.ok) {
            const err = await response.text();
            throw new Error(`Gemini API Error ${response.status}: ${err}`);
        }

        const data = await response.json();
        return data.candidates?.[0]?.content?.parts?.[0]?.text || "No response generated.";
    }
}
