// Task Team — Tasks CRUD with Redis Caching + Input Validation — 2026-03-29
const CACHE_TTL = 30; // seconds
const CACHE_PREFIX = "taskteam:tasks:";

// Validation constants
const VALID_STATUSES = ["pending", "in_progress", "done", "completed", "cancelled"];
const VALID_PRIORITIES = ["urgent", "high", "medium", "low"];
const MAX_TITLE_LENGTH = 500;
const MAX_DESCRIPTION_LENGTH = 5000;

// Input validation helper
function validateTaskInput(body, isUpdate = false) {
    const errors = [];

    if (!isUpdate) {
        // title is required for create
        if (!body.title || typeof body.title !== "string" || body.title.trim().length === 0) {
            errors.push("title is required and must be a non-empty string");
        }
    }

    if (body.title !== undefined) {
        if (typeof body.title !== "string") {
            errors.push("title must be a string");
        } else if (body.title.length > MAX_TITLE_LENGTH) {
            errors.push("title must not exceed " + MAX_TITLE_LENGTH + " characters");
        }
    }

    if (body.description !== undefined && body.description !== null) {
        if (typeof body.description !== "string") {
            errors.push("description must be a string");
        } else if (body.description.length > MAX_DESCRIPTION_LENGTH) {
            errors.push("description must not exceed " + MAX_DESCRIPTION_LENGTH + " characters");
        }
    }

    if (body.status !== undefined && body.status !== null) {
        if (!VALID_STATUSES.includes(body.status)) {
            errors.push("status must be one of: " + VALID_STATUSES.join(", "));
        }
    }

    if (body.priority !== undefined && body.priority !== null) {
        if (!VALID_PRIORITIES.includes(body.priority)) {
            errors.push("priority must be one of: " + VALID_PRIORITIES.join(", "));
        }
    }

    return errors;
}

async function taskRoutes(app) {

    // Helper: build cache key from query params
    function cacheKey(query) {
        const { status, group_id, limit = 50, offset = 0 } = query;
        return CACHE_PREFIX + "list:" + [status || "", group_id || "", limit, offset].join(":");
    }

    // Helper: invalidate all task list caches
    async function invalidateTaskCaches() {
        try {
            const keys = await app.redis.keys(CACHE_PREFIX + "*");
            if (keys.length > 0) {
                await app.redis.del(...keys);
                app.log.info("Invalidated " + keys.length + " task cache keys");
            }
        } catch (err) {
            app.log.warn("Cache invalidation error: " + err.message);
        }
    }

    // List tasks (cached)
    app.get("/tasks", async (req, reply) => {
        const key = cacheKey(req.query);

        // Try cache first
        try {
            const cached = await app.redis.get(key);
            if (cached) {
                reply.header("X-Cache", "HIT");
                return JSON.parse(cached);
            }
        } catch (err) {
            app.log.warn("Cache read error: " + err.message);
        }

        const { status, group_id, limit = 50, offset = 0 } = req.query;
        let query = "SELECT t.*, tg.name as group_name, tg.color as group_color, tg.icon as group_icon FROM tasks t LEFT JOIN task_groups tg ON t.group_id = tg.id WHERE 1=1";
        const params = [];
        if (status) { params.push(status); query += ` AND t.status = $${params.length}`; }
        if (group_id) { params.push(group_id); query += ` AND t.group_id = $${params.length}`; }
        query += " ORDER BY t.scheduled_at ASC NULLS LAST, t.priority DESC, t.created_at DESC";
        params.push(limit); query += ` LIMIT $${params.length}`;
        params.push(offset); query += ` OFFSET $${params.length}`;
        const { rows } = await app.db.query(query, params);
        const result = { data: rows, total: rows.length };

        // Store in cache
        try {
            await app.redis.set(key, JSON.stringify(result), "EX", CACHE_TTL);
        } catch (err) {
            app.log.warn("Cache write error: " + err.message);
        }

        reply.header("X-Cache", "MISS");
        return result;
    });

    // Get single task (cached)
    app.get("/tasks/:id", async (req, reply) => {
        const key = CACHE_PREFIX + "detail:" + req.params.id;

        try {
            const cached = await app.redis.get(key);
            if (cached) {
                reply.header("X-Cache", "HIT");
                return JSON.parse(cached);
            }
        } catch (err) {
            app.log.warn("Cache read error: " + err.message);
        }

        const { rows } = await app.db.query(
            "SELECT t.*, tg.name as group_name, tg.color as group_color FROM tasks t LEFT JOIN task_groups tg ON t.group_id = tg.id WHERE t.id = $1",
            [req.params.id]
        );
        if (!rows.length) throw { statusCode: 404, message: "Task not found" };
        const result = { data: rows[0] };

        try {
            await app.redis.set(key, JSON.stringify(result), "EX", CACHE_TTL);
        } catch (err) {
            app.log.warn("Cache write error: " + err.message);
        }

        reply.header("X-Cache", "MISS");
        return result;
    });

    // Create task (with validation, invalidates cache)
    app.post("/tasks", async (req, reply) => {
        const errors = validateTaskInput(req.body, false);
        if (errors.length > 0) {
            return reply.status(400).send({ error: "Validation failed", details: errors, statusCode: 400 });
        }

        const { title, description, status, group_id, priority, scheduled_at, due_at, assigned_to } = req.body;
        const { rows } = await app.db.query(
            `INSERT INTO tasks (title, description, status, group_id, priority, scheduled_at, due_at, assigned_to)
             VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
            [title.trim(), description || "", status || "pending", group_id, priority || "medium", scheduled_at, due_at, assigned_to || []]
        );
        await invalidateTaskCaches();
        return { data: rows[0] };
    });

    // Update task (with validation, invalidates cache)
    app.put("/tasks/:id", async (req, reply) => {
        const errors = validateTaskInput(req.body, true);
        if (errors.length > 0) {
            return reply.status(400).send({ error: "Validation failed", details: errors, statusCode: 400 });
        }

        const fields = req.body;
        const sets = [];
        const params = [];
        let i = 1;
        for (const [key, value] of Object.entries(fields)) {
            if (["title","description","status","group_id","priority","scheduled_at","due_at","assigned_to","completed_at"].includes(key)) {
                sets.push(`${key} = $${i}`);
                params.push(key === "title" && typeof value === "string" ? value.trim() : value);
                i++;
            }
        }
        if (fields.status === "completed" && !fields.completed_at) {
            sets.push("completed_at = NOW()");
        }
        sets.push("updated_at = NOW()");
        params.push(req.params.id);
        const { rows } = await app.db.query(
            `UPDATE tasks SET ${sets.join(", ")} WHERE id = $${i} RETURNING *`, params
        );
        if (!rows.length) throw { statusCode: 404, message: "Task not found" };
        
        // Auto-create next occurrence for recurring tasks
        const updatedTask = rows[0];
        if ((fields.status === "completed" || fields.status === "done") && updatedTask.recurrence) {
            try {
                const rec = typeof updatedTask.recurrence === "string" ? JSON.parse(updatedTask.recurrence) : updatedTask.recurrence;
                let nextDate = new Date();
                if (rec.type === "daily") {
                    nextDate.setDate(nextDate.getDate() + 1);
                } else if (rec.type === "weekly") {
                    nextDate.setDate(nextDate.getDate() + 7);
                } else if (rec.type === "monthly") {
                    nextDate.setMonth(nextDate.getMonth() + 1);
                }
                if (rec.time) {
                    const [h, m] = rec.time.split(":");
                    nextDate.setHours(parseInt(h) || 8, parseInt(m) || 0, 0, 0);
                }
                await app.db.query(
                    `INSERT INTO tasks (title, description, status, group_id, priority, scheduled_at, due_at, assigned_to, recurrence, project_id)
                     VALUES ($1, $2, 'pending', $3, $4, $5, $6, $7, $8, $9)`,
                    [
                        updatedTask.title,
                        updatedTask.description,
                        updatedTask.group_id,
                        updatedTask.priority,
                        nextDate.toISOString(),
                        updatedTask.due_at ? new Date(new Date(updatedTask.due_at).getTime() + (nextDate.getTime() - Date.now())).toISOString() : null,
                        updatedTask.assigned_to || [],
                        JSON.stringify(rec),
                        updatedTask.project_id
                    ]
                );
                app.log.info("Auto-created next recurring task for: " + updatedTask.title);
            } catch (recErr) {
                app.log.warn("Recurrence auto-create failed: " + recErr.message);
            }
        }

        await invalidateTaskCaches();
        return { data: rows[0] };
    });

    // Delete task (invalidates cache)
    app.delete("/tasks/:id", async (req) => {
        const { rowCount } = await app.db.query("DELETE FROM tasks WHERE id = $1", [req.params.id]);
        if (!rowCount) throw { statusCode: 404, message: "Task not found" };
        await invalidateTaskCaches();
        return { status: "deleted" };
    });

    // Task comments
    app.get("/tasks/:id/comments", async (req) => {
        const { rows } = await app.db.query(
            "SELECT * FROM task_comments WHERE task_id = $1 ORDER BY created_at ASC", [req.params.id]
        );
        return { data: rows };
    });

    app.post("/tasks/:id/comments", async (req) => {
        const { content, is_ai } = req.body;
        const { rows } = await app.db.query(
            "INSERT INTO task_comments (task_id, content, is_ai) VALUES ($1, $2, $3) RETURNING *",
            [req.params.id, content, is_ai || false]
        );
        return { data: rows[0] };
    });


// === Task Recurrence Endpoints ===

// Set recurrence on a task
app.post("/tasks/:id/recurrence", async (req) => {
    const { type, days, time } = req.body;
    if (!type || !["daily", "weekly", "monthly"].includes(type)) {
        throw { statusCode: 400, message: "type must be one of: daily, weekly, monthly" };
    }
    const recurrence = { type, days: days || [], time: time || "08:00" };
    const { rows } = await app.db.query(
        "UPDATE tasks SET recurrence = $1, updated_at = NOW() WHERE id = $2 RETURNING *",
        [JSON.stringify(recurrence), req.params.id]
    );
    if (!rows.length) throw { statusCode: 404, message: "Task not found" };
    await invalidateTaskCaches();
    return { data: rows[0] };
});

// Remove recurrence from a task
app.delete("/tasks/:id/recurrence", async (req) => {
    const { rows } = await app.db.query(
        "UPDATE tasks SET recurrence = NULL, updated_at = NOW() WHERE id = $1 RETURNING *",
        [req.params.id]
    );
    if (!rows.length) throw { statusCode: 404, message: "Task not found" };
    await invalidateTaskCaches();
    return { data: rows[0] };
});

// List recurring tasks
app.get("/tasks/recurring", async (req) => {
    const { rows } = await app.db.query(
        `SELECT t.*, tg.name as group_name, tg.color as group_color, tg.icon as group_icon
         FROM tasks t LEFT JOIN task_groups tg ON t.group_id = tg.id
         WHERE t.recurrence IS NOT NULL
         ORDER BY t.created_at DESC`
    );
    return { data: rows };
});

}

module.exports = taskRoutes;
