import { Request, Response } from "express";
import { storage } from "./storage";
import { User } from "../shared/schema";
import { randomBytes } from "crypto";

interface JsonRpcRequest {
    jsonrpc: "2.0";
    method: string;
    params?: any;
    id: number | string;
}

interface JsonRpcResponse {
    jsonrpc: "2.0";
    result?: any;
    error?: {
        code: number;
        message: string;
        data?: any;
    };
    id: number | string | null;
}

export class McpServer {
    private clients: Map<string, Response> = new Map();

    async authenticate(req: Request): Promise<User | null> {
        let key = req.headers["x-api-key"] as string;
        if (!key && req.query.apiKey) {
            key = req.query.apiKey as string;
        }
        if (!key && req.headers["authorization"]) {
            const auth = req.headers["authorization"];
            if (auth.startsWith("Bearer ")) {
                key = auth.substring(7);
            }
        }
        if (!key) return null;
        return (await storage.getUserByApiKey(key)) || null;
    }

    async handleSse(req: Request, res: Response) {
        const user = await this.authenticate(req);
        if (!user) {
            res.status(401).send("Unauthorized: Invalid API Key");
            return;
        }

        res.writeHead(200, {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Access-Control-Allow-Origin": "*",
        });

        const sessionId = randomBytes(8).toString("hex");
        this.clients.set(sessionId, res);

        const endpoint = `/api/mcp/messages`;
        res.write(`event: endpoint\ndata: ${endpoint}\n\n`);

        const interval = setInterval(() => {
            res.write(": keepalive\n\n");
        }, 15000);

        req.on("close", () => {
            clearInterval(interval);
            this.clients.delete(sessionId);
        });
    }

    async handleMessage(req: Request, res: Response) {
        const user = await this.authenticate(req);
        if (!user) {
            res.status(401).json({ error: "Unauthorized" });
            return;
        }

        const body = req.body as JsonRpcRequest;
        try {
            const result = await this.processRequest(body, user);
            res.json({
                jsonrpc: "2.0",
                result,
                id: body.id
            });
        } catch (err: any) {
            res.json({
                jsonrpc: "2.0",
                error: {
                    code: -32000,
                    message: err.message || "Internal Server Error"
                },
                id: body.id
            });
        }
    }

    private async processRequest(req: JsonRpcRequest, user: User): Promise<any> {
        switch (req.method) {
            case "initialize":
                return {
                    protocolVersion: "2024-11-05",
                    capabilities: { tools: {}, resources: {} },
                    serverInfo: { name: "TaskFlow MCP", version: "2.0.0" }
                };

            case "tools/list":
            case "listTools":
                return { tools: this.getToolDefinitions() };

            case "tools/call":
            case "callTool":
                return await this.handleToolCall(req.params.name, req.params.arguments, user);

            case "notifications/initialized":
                return true;

            default:
                throw new Error(`Method ${req.method} not found`);
        }
    }

    private getToolDefinitions() {
        return [
            // ===== TASK MANAGEMENT =====
            {
                name: "list_tasks",
                description: "List all tasks. Optional filters: status, priority, labelId, limit.",
                inputSchema: {
                    type: "object",
                    properties: {
                        status: { type: "string", enum: ["todo", "inProgress", "done"], description: "Filter by status" },
                        priority: { type: "string", enum: ["low", "medium", "high"], description: "Filter by priority" },
                        labelId: { type: "string", description: "Filter by label ID" },
                        limit: { type: "number", description: "Max results" },
                        includeCompleted: { type: "boolean", description: "Include done tasks (default: false)" }
                    }
                }
            },
            {
                name: "get_task",
                description: "Get a single task by ID with full details.",
                inputSchema: {
                    type: "object",
                    properties: { id: { type: "string", description: "Task ID" } },
                    required: ["id"]
                }
            },
            {
                name: "search_tasks",
                description: "Search tasks by title/description text.",
                inputSchema: {
                    type: "object",
                    properties: { query: { type: "string", description: "Search text" } },
                    required: ["query"]
                }
            },
            {
                name: "create_task",
                description: "Create a new task. Returns the created task with ID.",
                inputSchema: {
                    type: "object",
                    properties: {
                        title: { type: "string", description: "Task title" },
                        description: { type: "string", description: "Task description (markdown supported)" },
                        priority: { type: "string", enum: ["low", "medium", "high"], description: "Priority level" },
                        status: { type: "string", enum: ["todo", "inProgress", "done"], description: "Initial status" },
                        dueDate: { type: "string", description: "Due date (ISO 8601)" },
                        labelId: { type: "string", description: "Label/category ID" },
                        estimatedDuration: { type: "number", description: "Estimated minutes" },
                        energyLevel: { type: "string", enum: ["low", "medium", "high"], description: "Required energy level" },
                        parentTaskId: { type: "string", description: "Parent task ID (for subtasks)" },
                        notes: { type: "string", description: "Additional notes" }
                    },
                    required: ["title"]
                }
            },
            {
                name: "update_task",
                description: "Update an existing task. Only provided fields are changed.",
                inputSchema: {
                    type: "object",
                    properties: {
                        id: { type: "string", description: "Task ID" },
                        title: { type: "string" },
                        description: { type: "string" },
                        priority: { type: "string", enum: ["low", "medium", "high"] },
                        status: { type: "string", enum: ["todo", "inProgress", "done"] },
                        dueDate: { type: "string", description: "Due date (ISO 8601) or null to clear" },
                        labelId: { type: "string", description: "Label ID or null to clear" },
                        estimatedDuration: { type: "number" },
                        energyLevel: { type: "string", enum: ["low", "medium", "high"] },
                        notes: { type: "string" }
                    },
                    required: ["id"]
                }
            },
            {
                name: "complete_task",
                description: "Mark a task as done.",
                inputSchema: {
                    type: "object",
                    properties: { id: { type: "string", description: "Task ID" } },
                    required: ["id"]
                }
            },
            {
                name: "delete_task",
                description: "Permanently delete a task.",
                inputSchema: {
                    type: "object",
                    properties: { id: { type: "string", description: "Task ID" } },
                    required: ["id"]
                }
            },

            // ===== LABELS =====
            {
                name: "list_labels",
                description: "List all available labels/categories.",
                inputSchema: { type: "object", properties: {} }
            },
            {
                name: "create_label",
                description: "Create a new label/category.",
                inputSchema: {
                    type: "object",
                    properties: {
                        name: { type: "string", description: "Label name" },
                        color: { type: "string", description: "Hex color (e.g. #ff5733)" },
                        domain: { type: "string", enum: ["work", "personal", "neutral"], description: "Domain" }
                    },
                    required: ["name", "color"]
                }
            },

            // ===== USER & STATS =====
            {
                name: "get_user_stats",
                description: "Get current user stats: XP, level, streak, task counts.",
                inputSchema: { type: "object", properties: {} }
            },
            {
                name: "get_dashboard",
                description: "Get a dashboard overview: active tasks, upcoming due, stats.",
                inputSchema: { type: "object", properties: {} }
            },

            // ===== BULK OPERATIONS =====
            {
                name: "bulk_create_tasks",
                description: "Create multiple tasks at once. Returns array of created tasks.",
                inputSchema: {
                    type: "object",
                    properties: {
                        tasks: {
                            type: "array",
                            items: {
                                type: "object",
                                properties: {
                                    title: { type: "string" },
                                    description: { type: "string" },
                                    priority: { type: "string", enum: ["low", "medium", "high"] },
                                    dueDate: { type: "string" },
                                    labelId: { type: "string" },
                                    estimatedDuration: { type: "number" }
                                },
                                required: ["title"]
                            },
                            description: "Array of task objects"
                        }
                    },
                    required: ["tasks"]
                }
            },
            {
                name: "bulk_update_tasks",
                description: "Update multiple tasks. Each item needs an id + fields to change.",
                inputSchema: {
                    type: "object",
                    properties: {
                        updates: {
                            type: "array",
                            items: {
                                type: "object",
                                properties: {
                                    id: { type: "string" },
                                    status: { type: "string" },
                                    priority: { type: "string" },
                                    dueDate: { type: "string" }
                                },
                                required: ["id"]
                            }
                        }
                    },
                    required: ["updates"]
                }
            }
        ];
    }

    private async handleToolCall(name: string, args: any, user: User) {
        switch (name) {
            case "list_tasks": {
                let tasks = await storage.getTasksForUser(user.id);
                if (!args?.includeCompleted) {
                    tasks = tasks.filter(t => t.status !== "done");
                }
                if (args?.status) tasks = tasks.filter(t => t.status === args.status);
                if (args?.priority) tasks = tasks.filter(t => t.priority === args.priority);
                if (args?.labelId) tasks = tasks.filter(t => t.labelId === args.labelId);
                if (args?.limit) tasks = tasks.slice(0, args.limit);
                
                // Return compact format
                const compact = tasks.map(t => ({
                    id: t.id,
                    title: t.title,
                    status: t.status,
                    priority: t.priority,
                    dueDate: t.dueDate,
                    labelId: t.labelId,
                    estimatedDuration: t.estimatedDuration,
                    timeTracked: t.timeTracked,
                    energyLevel: t.energyLevel
                }));
                return { content: [{ type: "text", text: JSON.stringify(compact, null, 2) }] };
            }

            case "get_task": {
                if (!args.id) throw new Error("Task ID is required");
                const task = await storage.getTask(args.id);
                if (!task || task.userId !== user.id) throw new Error("Task not found");
                return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
            }

            case "search_tasks": {
                if (!args.query) throw new Error("Search query is required");
                const tasks = await storage.searchTasks(args.query, user.id);
                const compact = tasks.map(t => ({
                    id: t.id, title: t.title, status: t.status,
                    priority: t.priority, dueDate: t.dueDate
                }));
                return { content: [{ type: "text", text: JSON.stringify(compact, null, 2) }] };
            }

            case "create_task": {
                if (!args.title) throw new Error("Title is required");
                const task = await storage.createTask({
                    title: args.title,
                    description: args.description || "",
                    priority: args.priority || "medium",
                    status: args.status || "todo",
                    isTracking: false,
                    timeTracked: 0,
                    energyLevel: args.energyLevel || "medium",
                    estimatedDuration: args.estimatedDuration || 15,
                    dueDate: args.dueDate || null,
                    notes: args.notes || "",
                    labelId: args.labelId || null,
                    parentTaskId: args.parentTaskId || null,
                    userId: user.id
                });

                await storage.createAuditLog({
                    userId: user.id,
                    action: "CREATE",
                    entityType: "TASK",
                    entityId: task.id,
                    details: { title: task.title, source: "MCP/API" },
                    source: "API"
                });

                return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
            }

            case "update_task": {
                if (!args.id) throw new Error("Task ID is required");
                const task = await storage.getTask(args.id);
                if (!task || task.userId !== user.id) throw new Error("Task not found");

                const { id, ...updates } = args;
                const updated = await storage.updateTask(id, updates);

                await storage.createAuditLog({
                    userId: user.id,
                    action: "UPDATE",
                    entityType: "TASK",
                    entityId: id,
                    details: { ...updates, source: "MCP/API" },
                    source: "API"
                });

                return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
            }

            case "complete_task": {
                if (!args.id) throw new Error("Task ID is required");
                const task = await storage.getTask(args.id);
                if (!task || task.userId !== user.id) throw new Error("Task not found");
                const updated = await storage.updateTask(args.id, { status: "done" });

                await storage.createAuditLog({
                    userId: user.id,
                    action: "UPDATE",
                    entityType: "TASK",
                    entityId: args.id,
                    details: { status: "done", source: "MCP/API" },
                    source: "API"
                });

                return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
            }

            case "delete_task": {
                if (!args.id) throw new Error("Task ID is required");
                const task = await storage.getTask(args.id);
                if (!task || task.userId !== user.id) throw new Error("Task not found");
                await storage.deleteTask(args.id);

                await storage.createAuditLog({
                    userId: user.id,
                    action: "DELETE",
                    entityType: "TASK",
                    entityId: args.id,
                    details: { title: task.title, source: "MCP/API" },
                    source: "API"
                });

                return { content: [{ type: "text", text: `Task "${task.title}" deleted.` }] };
            }

            case "list_labels": {
                const labels = await storage.getAllLabels();
                const visible = labels.filter(l =>
                    l.creatorId === user.id || l.creatorId === null
                );
                return { content: [{ type: "text", text: JSON.stringify(visible, null, 2) }] };
            }

            case "create_label": {
                if (!args.name || !args.color) throw new Error("Name and color are required");
                const label = await storage.createLabel({
                    name: args.name,
                    color: args.color,
                    domain: args.domain || "neutral",
                    creatorId: user.id
                });
                return { content: [{ type: "text", text: JSON.stringify(label, null, 2) }] };
            }

            case "get_user_stats": {
                const freshUser = await storage.getUser(user.id);
                if (!freshUser) throw new Error("User not found");
                const tasks = await storage.getTasksForUser(user.id);
                const active = tasks.filter(t => t.status !== "done");
                const done = tasks.filter(t => t.status === "done");
                const overdue = active.filter(t => t.dueDate && new Date(t.dueDate) < new Date());

                return {
                    content: [{
                        type: "text", text: JSON.stringify({
                            username: freshUser.username,
                            xp: freshUser.xp,
                            level: freshUser.level,
                            streak: freshUser.currentStreak,
                            tasks: {
                                total: tasks.length,
                                active: active.length,
                                completed: done.length,
                                overdue: overdue.length,
                                byPriority: {
                                    high: active.filter(t => t.priority === "high").length,
                                    medium: active.filter(t => t.priority === "medium").length,
                                    low: active.filter(t => t.priority === "low").length
                                }
                            }
                        }, null, 2)
                    }]
                };
            }

            case "get_dashboard": {
                const tasks = await storage.getTasksForUser(user.id);
                const labels = await storage.getAllLabels();
                const now = new Date();
                const in48h = new Date(now.getTime() + 48 * 60 * 60 * 1000);

                const active = tasks.filter(t => t.status !== "done");
                const upcoming = active
                    .filter(t => t.dueDate && new Date(t.dueDate) <= in48h)
                    .sort((a, b) => new Date(a.dueDate!).getTime() - new Date(b.dueDate!).getTime());
                const overdue = active.filter(t => t.dueDate && new Date(t.dueDate) < now);
                const highPriority = active.filter(t => t.priority === "high");

                const labelMap = new Map(labels.map(l => [l.id, l.name]));

                return {
                    content: [{
                        type: "text", text: JSON.stringify({
                            summary: {
                                activeTasks: active.length,
                                overdue: overdue.length,
                                dueSoon: upcoming.length,
                                highPriority: highPriority.length
                            },
                            overdueTasks: overdue.map(t => ({
                                id: t.id, title: t.title, dueDate: t.dueDate,
                                priority: t.priority, label: labelMap.get(t.labelId || "") || null
                            })),
                            upcomingTasks: upcoming.slice(0, 10).map(t => ({
                                id: t.id, title: t.title, dueDate: t.dueDate,
                                priority: t.priority, label: labelMap.get(t.labelId || "") || null
                            })),
                            highPriorityTasks: highPriority.map(t => ({
                                id: t.id, title: t.title, dueDate: t.dueDate,
                                label: labelMap.get(t.labelId || "") || null
                            }))
                        }, null, 2)
                    }]
                };
            }

            case "bulk_create_tasks": {
                if (!args.tasks || !Array.isArray(args.tasks)) throw new Error("tasks array is required");
                const created = [];
                for (const t of args.tasks) {
                    if (!t.title) continue;
                    const task = await storage.createTask({
                        title: t.title,
                        description: t.description || "",
                        priority: t.priority || "medium",
                        status: "todo",
                        isTracking: false,
                        timeTracked: 0,
                        energyLevel: t.energyLevel || "medium",
                        estimatedDuration: t.estimatedDuration || 15,
                        dueDate: t.dueDate || null,
                        notes: "",
                        labelId: t.labelId || null,
                        userId: user.id
                    });
                    created.push({ id: task.id, title: task.title });
                }
                return { content: [{ type: "text", text: JSON.stringify({ created: created.length, tasks: created }, null, 2) }] };
            }

            case "bulk_update_tasks": {
                if (!args.updates || !Array.isArray(args.updates)) throw new Error("updates array is required");
                const results = [];
                for (const u of args.updates) {
                    if (!u.id) continue;
                    const task = await storage.getTask(u.id);
                    if (!task || task.userId !== user.id) continue;
                    const { id, ...updates } = u;
                    const updated = await storage.updateTask(id, updates);
                    results.push({ id: updated?.id, title: updated?.title, status: updated?.status });
                }
                return { content: [{ type: "text", text: JSON.stringify({ updated: results.length, tasks: results }, null, 2) }] };
            }

            default:
                throw new Error(`Tool ${name} not implemented`);
        }
    }
}

export const mcpServer = new McpServer();
