const { Task } = require('../../../models');
const { Op } = require('sequelize');
const moment = require('moment-timezone');
const permissionsService = require('../../../services/permissionsService');
const {
    countTotalOpenTasks,
    countTasksPendingOverMonth,
    fetchTasksInProgress,
    fetchTodayPlanTasks,
    fetchTasksDueToday,
    fetchOverdueTasks,
    fetchSomedayTaskIds,
    fetchNonProjectTasks,
    fetchProjectTasks,
    fetchSomedayFallbackTasks,
    fetchTasksCompletedToday,
} = require('./metrics-queries');

const MAX_SUGGESTED_TASKS = 10;

const getPriorityValue = (priority) => {
    const priorityOrder = {
        high: 3,
        medium: 2,
        low: 1,
    };

    if (priority === null || priority === undefined) {
        return 0;
    }

    if (typeof priority === 'number') {
        // Normalize numeric priority (assuming 2=high,1=medium,0=low)
        if (priority >= 2) return priorityOrder.high;
        if (priority === 1) return priorityOrder.medium;
        if (priority === 0) return priorityOrder.low;
        return 0;
    }

    const normalized = String(priority).toLowerCase();
    return priorityOrder[normalized] || 0;
};

const multiCriteriaTaskSort = (a, b) => {
    // 1. Priority
    const priorityDiff =
        getPriorityValue(b.priority) - getPriorityValue(a.priority);
    if (priorityDiff !== 0) {
        return priorityDiff;
    }

    // 2. Due date (earlier first, null/undefined last)
    const getDueDateValue = (task) => {
        if (!task.due_date) return Infinity;
        const due =
            task.due_date instanceof Date
                ? task.due_date
                : new Date(task.due_date);
        const time = due.getTime();
        return Number.isNaN(time) ? Infinity : time;
    };

    const dueDiff = getDueDateValue(a) - getDueDateValue(b);
    if (dueDiff !== 0) {
        return dueDiff;
    }

    // 3. Project (group similar project tasks)
    const projectA = (a.project_id || '').toString();
    const projectB = (b.project_id || '').toString();
    return projectA.localeCompare(projectB);
};

async function computeSuggestedTasks(
    visibleTasksWhere,
    userId,
    totalOpenTasks,
    tasksInProgress,
    tasksDueToday,
    tasksOverdue,
    todayPlanTasks
) {
    if (
        totalOpenTasks < 3 &&
        tasksInProgress.length === 0 &&
        tasksDueToday.length === 0
    ) {
        return [];
    }

    const excludedTaskIds = [
        ...tasksInProgress.map((t) => t.id),
        ...tasksDueToday.map((t) => t.id),
        ...tasksOverdue.map((t) => t.id),
        ...todayPlanTasks.map((t) => t.id),
    ];

    const somedayTaskIds = await fetchSomedayTaskIds(userId);

    const [nonProjectTasks, projectTasks] = await Promise.all([
        fetchNonProjectTasks(
            visibleTasksWhere,
            excludedTaskIds,
            somedayTaskIds
        ),
        fetchProjectTasks(visibleTasksWhere, excludedTaskIds, somedayTaskIds),
    ]);

    let combinedTasks = [...nonProjectTasks, ...projectTasks];

    if (combinedTasks.length < 6) {
        const usedTaskIds = [
            ...excludedTaskIds,
            ...combinedTasks.map((t) => t.id),
        ];

        const somedayFallbackTasks = await fetchSomedayFallbackTasks(
            userId,
            usedTaskIds,
            somedayTaskIds
        );

        combinedTasks = [...combinedTasks, ...somedayFallbackTasks];
    }

    const now = Date.now();
    const filteredTasks = combinedTasks.filter((task) => {
        if (!task.defer_until) return true;
        const deferUntil = new Date(task.defer_until).getTime();
        if (Number.isNaN(deferUntil)) return true;
        return deferUntil <= now;
    });

    filteredTasks.sort(multiCriteriaTaskSort);

    return filteredTasks.slice(0, MAX_SUGGESTED_TASKS);
}

async function computeWeeklyCompletions(userId, userTimezone) {
    const todayInUserTz = moment.tz(userTimezone);
    const weekStartInUserTz = moment.tz(userTimezone).subtract(6, 'days');
    const weekStart = weekStartInUserTz.clone().startOf('day').utc().toDate();
    const weekEnd = todayInUserTz.clone().endOf('day').utc().toDate();

    const weeklyCompletionsRaw = await Task.findAll({
        where: {
            user_id: userId,
            status: Task.STATUS.DONE,
            completed_at: {
                [Op.between]: [weekStart, weekEnd],
            },
        },
        attributes: ['completed_at'],
        raw: true,
    });

    const dateCountMap = {};
    weeklyCompletionsRaw.forEach((task) => {
        const completedDate = new Date(task.completed_at);
        const dateInUserTz = moment(completedDate)
            .tz(userTimezone)
            .format('YYYY-MM-DD');
        dateCountMap[dateInUserTz] = (dateCountMap[dateInUserTz] || 0) + 1;
    });

    const weeklyCompletions = Object.entries(dateCountMap).map(
        ([date, count]) => ({
            date,
            count: count.toString(),
        })
    );

    const weeklyData = [];
    for (let i = 6; i >= 0; i--) {
        const dateInUserTz = moment.tz(userTimezone).subtract(i, 'days');
        const dateString = dateInUserTz.format('YYYY-MM-DD');

        const found = weeklyCompletions.find(
            (item) => item.date === dateString
        );
        const dayData = {
            date: dateString,
            count: found ? parseInt(found.count) : 0,
            dayName: dateInUserTz.format('ddd'),
        };
        weeklyData.push(dayData);
    }

    return weeklyData;
}

async function computeTaskMetrics(
    userId,
    userTimezone = 'UTC',
    permissionCache = null
) {
    const visibleTasksWhere =
        await permissionsService.ownershipOrPermissionWhere(
            'task',
            userId,
            permissionCache
        );

    const [
        totalOpenTasks,
        tasksPendingOverMonth,
        tasksInProgress,
        todayPlanTasks,
        tasksDueToday,
        tasksOverdue,
        tasksCompletedToday,
        weeklyCompletions,
    ] = await Promise.all([
        countTotalOpenTasks(visibleTasksWhere),
        countTasksPendingOverMonth(visibleTasksWhere),
        fetchTasksInProgress(visibleTasksWhere),
        fetchTodayPlanTasks(visibleTasksWhere),
        fetchTasksDueToday(visibleTasksWhere, userTimezone),
        fetchOverdueTasks(visibleTasksWhere, userTimezone),
        fetchTasksCompletedToday(userId, userTimezone),
        computeWeeklyCompletions(userId, userTimezone),
    ]);

    const suggestedTasks = await computeSuggestedTasks(
        visibleTasksWhere,
        userId,
        totalOpenTasks,
        tasksInProgress,
        tasksDueToday,
        tasksOverdue,
        todayPlanTasks
    );

    return {
        total_open_tasks: totalOpenTasks,
        tasks_pending_over_month: tasksPendingOverMonth,
        tasks_in_progress_count: tasksInProgress.length,
        tasks_in_progress: tasksInProgress,
        tasks_due_today: tasksDueToday,
        tasks_overdue: tasksOverdue,
        today_plan_tasks: todayPlanTasks,
        suggested_tasks: suggestedTasks,
        tasks_completed_today: tasksCompletedToday,
        weekly_completions: weeklyCompletions,
    };
}

async function getTaskMetrics(userId, timezone) {
    const metrics = await computeTaskMetrics(userId, timezone);
    const {
        buildMetricsResponse,
        serializeTasks,
    } = require('../core/serializers');

    const response = await buildMetricsResponse(metrics);

    const serializedLists = {
        tasks_in_progress: await serializeTasks(
            metrics.tasks_in_progress,
            timezone
        ),
        tasks_today_plan: await serializeTasks(
            metrics.today_plan_tasks,
            timezone
        ),
        tasks_due_today: await serializeTasks(
            metrics.tasks_due_today,
            timezone
        ),
        tasks_overdue: await serializeTasks(metrics.tasks_overdue, timezone),
        suggested_tasks: await serializeTasks(
            metrics.suggested_tasks,
            timezone
        ),
        tasks_completed_today: await serializeTasks(
            metrics.tasks_completed_today,
            timezone
        ),
    };

    Object.assign(response, serializedLists);
    response.dashboard_lists = serializedLists;

    return response;
}

module.exports = {
    computeSuggestedTasks,
    computeWeeklyCompletions,
    computeTaskMetrics,
    getTaskMetrics,
};
