import { ParsedQs } from "qs";

import db from "../config/db";
import HandleExceptions from "../decorators/handle-exceptions";
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
import { ServerResponse } from "../models/server-response";
import { TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA, UNMAPPED } from "../shared/constants";
import { getColor, log_error } from "../shared/utils";
import TasksControllerBase, { GroupBy, ITaskGroup } from "./tasks-controller-base";

export class TaskListGroup implements ITaskGroup {
  name: string;
  category_id: string | null;
  color_code: string;
  color_code_dark: string;
  start_date?: string;
  end_date?: string;
  todo_progress: number;
  doing_progress: number;
  done_progress: number;
  tasks: any[];

  constructor(group: any) {
    this.name = group.name;
    this.category_id = group.category_id || null;
    this.start_date = group.start_date || null;
    this.end_date = group.end_date || null;
    this.color_code = group.color_code + TASK_STATUS_COLOR_ALPHA;
    this.color_code_dark = group.color_code_dark;
    this.todo_progress = 0;
    this.doing_progress = 0;
    this.done_progress = 0;
    this.tasks = [];
  }
}

export default class TasksControllerV2 extends TasksControllerBase {
  private static isCountsOnly(query: ParsedQs) {
    return query.count === "true";
  }

  public static isTasksOnlyReq(query: ParsedQs) {
    return TasksControllerV2.isCountsOnly(query) || query.parent_task;
  }

  private static flatString(text: string) {
    return (text || "").split(" ").map(s => `'${s}'`).join(",");
  }

  private static getFilterByStatusWhereClosure(text: string) {
    return text ? `status_id IN (${this.flatString(text)})` : "";
  }

  private static getFilterByPriorityWhereClosure(text: string) {
    return text ? `priority_id IN (${this.flatString(text)})` : "";
  }

  private static getFilterByLabelsWhereClosure(text: string) {
    return text
      ? `id IN (SELECT task_id FROM task_labels WHERE label_id IN (${this.flatString(text)}))`
      : "";
  }

  private static getFilterByMembersWhereClosure(text: string) {
    return text
      ? `id IN (SELECT task_id FROM tasks_assignees WHERE team_member_id IN (${this.flatString(text)}))`
      : "";
  }

  private static getFilterByProjectsWhereClosure(text: string) {
    return text ? `project_id IN (${this.flatString(text)})` : "";
  }

  private static getFilterByAssignee(filterBy: string) {
    return filterBy === "member"
      ? `id IN (SELECT task_id FROM tasks_assignees WHERE team_member_id = $1)`
      : "project_id = $1";
  }

  private static getStatusesQuery(filterBy: string) {
    return filterBy === "member"
      ? `, (SELECT COALESCE(JSON_AGG(rec), '[]'::JSON)
      FROM (SELECT task_statuses.id, task_statuses.name, stsc.color_code
          FROM task_statuses
              INNER JOIN sys_task_status_categories stsc ON task_statuses.category_id = stsc.id
          WHERE project_id = t.project_id
          ORDER BY task_statuses.name) rec) AS statuses`
      : "";
  }

  public static async getTaskCompleteRatio(taskId: string): Promise<{
    ratio: number;
    total_completed: number;
    total_tasks: number;
  } | null> {
    try {
      const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]);
      const [data] = result.rows;
      if (data && data.info && data.info.ratio !== undefined) {
        data.info.ratio = +((data.info.ratio || 0).toFixed());
        return data.info;
      }
      return null;
    } catch (error) {
      log_error(`Error in getTaskCompleteRatio: ${error}`);
      return null;
    }
  }

  private static getQuery(userId: string, options: ParsedQs) {
    const searchField = options.search ? "t.name" : "sort_order";
    const { searchQuery, sortField } = TasksControllerV2.toPaginationOptions(options, searchField);

    const isSubTasks = !!options.parent_task;

    const sortFields = sortField.replace(/ascend/g, "ASC").replace(/descend/g, "DESC") || "sort_order";

    // Filter tasks by statuses
    const statusesFilter = TasksControllerV2.getFilterByStatusWhereClosure(options.statuses as string);
    // Filter tasks by labels
    const labelsFilter = TasksControllerV2.getFilterByLabelsWhereClosure(options.labels as string);
    // Filter tasks by its members
    const membersFilter = TasksControllerV2.getFilterByMembersWhereClosure(options.members as string);
    // Filter tasks by projects
    const projectsFilter = TasksControllerV2.getFilterByProjectsWhereClosure(options.projects as string);
    // Filter tasks by priorities
    const priorityFilter = TasksControllerV2.getFilterByPriorityWhereClosure(options.priorities as string);
    // Filter tasks by a single assignee
    const filterByAssignee = TasksControllerV2.getFilterByAssignee(options.filterBy as string);
    // Returns statuses of each task as a json array if filterBy === "member"
    const statusesQuery = TasksControllerV2.getStatusesQuery(options.filterBy as string);
    
    // Custom columns data query
    const customColumnsQuery = options.customColumns 
      ? `, (SELECT COALESCE(
            jsonb_object_agg(
              custom_cols.key, 
              custom_cols.value
            ), 
            '{}'::JSONB
          )
          FROM (
            SELECT 
              cc.key,
              CASE 
                WHEN ccv.text_value IS NOT NULL THEN to_jsonb(ccv.text_value)
                WHEN ccv.number_value IS NOT NULL THEN to_jsonb(ccv.number_value)
                WHEN ccv.boolean_value IS NOT NULL THEN to_jsonb(ccv.boolean_value)
                WHEN ccv.date_value IS NOT NULL THEN to_jsonb(ccv.date_value)
                WHEN ccv.json_value IS NOT NULL THEN ccv.json_value
                ELSE NULL::JSONB
              END AS value
            FROM cc_column_values ccv
            JOIN cc_custom_columns cc ON ccv.column_id = cc.id
            WHERE ccv.task_id = t.id
          ) AS custom_cols
          WHERE custom_cols.value IS NOT NULL) AS custom_column_values`
      : "";

    const archivedFilter = options.archived === "true" ? "archived IS TRUE" : "archived IS FALSE";

    let subTasksFilter;

    if (options.isSubtasksInclude === "true") {
      subTasksFilter = "";
    } else {
      subTasksFilter = isSubTasks ? "parent_task_id = $2" : "parent_task_id IS NULL";
    }

    const filters = [
      subTasksFilter,
      (isSubTasks ? "1 = 1" : archivedFilter),
      (isSubTasks ? "$1 = $1" : filterByAssignee), // ignored filter by member in peoples page for sub-tasks
      statusesFilter,
      priorityFilter,
      labelsFilter,
      membersFilter,
      projectsFilter
    ].filter(i => !!i).join(" AND ");

    return `
      SELECT id,
             name,
             CONCAT((SELECT key FROM projects WHERE id = t.project_id), '-', task_no) AS task_key,
             (SELECT name FROM projects WHERE id = t.project_id) AS project_name,
             t.project_id AS project_id,
             t.parent_task_id,
             t.parent_task_id IS NOT NULL AS is_sub_task,
             (SELECT name FROM tasks WHERE id = t.parent_task_id) AS parent_task_name,
             (SELECT COUNT(*)
              FROM tasks
              WHERE parent_task_id = t.id)::INT AS sub_tasks_count,

             t.status_id AS status,
             t.archived,
             t.description,
             t.sort_order,
             t.progress_value,
             t.manual_progress,
             t.weight,
             (SELECT use_manual_progress FROM projects WHERE id = t.project_id) AS project_use_manual_progress,
             (SELECT use_weighted_progress FROM projects WHERE id = t.project_id) AS project_use_weighted_progress,
             (SELECT use_time_progress FROM projects WHERE id = t.project_id) AS project_use_time_progress,
             (SELECT get_task_complete_ratio(t.id)->>'ratio') AS complete_ratio,

             (SELECT phase_id FROM task_phase WHERE task_id = t.id) AS phase_id,
             (SELECT name
              FROM project_phases
              WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = t.id)) AS phase_name,
              (SELECT color_code
                FROM project_phases
                WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = t.id)) AS phase_color_code,

             (EXISTS(SELECT 1 FROM task_subscribers WHERE task_id = t.id)) AS has_subscribers,
             (EXISTS(SELECT 1 FROM task_dependencies td WHERE td.task_id = t.id)) AS has_dependencies,
             (SELECT start_time
              FROM task_timers
              WHERE task_id = t.id
                AND user_id = '${userId}') AS timer_start_time,

             (SELECT color_code
              FROM sys_task_status_categories
              WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color,

             (SELECT color_code_dark
              FROM sys_task_status_categories
              WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color_dark,

             (SELECT COALESCE(ROW_TO_JSON(r), '{}'::JSON)
              FROM (SELECT is_done, is_doing, is_todo
                    FROM sys_task_status_categories
                    WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) r) AS status_category,

             (SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) AS comments_count,
             (SELECT COUNT(*) FROM task_attachments WHERE task_id = t.id) AS attachments_count,
             (CASE
                WHEN EXISTS(SELECT 1
                            FROM tasks_with_status_view
                            WHERE tasks_with_status_view.task_id = t.id
                              AND is_done IS TRUE) THEN 1
                ELSE 0 END) AS parent_task_completed,
             (SELECT get_task_assignees(t.id)) AS assignees,
             (SELECT COUNT(*)
              FROM tasks_with_status_view tt
              WHERE tt.parent_task_id = t.id
                AND tt.is_done IS TRUE)::INT
               AS completed_sub_tasks,

             (SELECT COALESCE(JSON_AGG(r), '[]'::JSON)
              FROM (SELECT task_labels.label_id AS id,
                           (SELECT name FROM team_labels WHERE id = task_labels.label_id),
                           (SELECT color_code FROM team_labels WHERE id = task_labels.label_id)
                    FROM task_labels
                    WHERE task_id = t.id) r) AS labels,
             (SELECT is_completed(status_id, project_id)) AS is_complete,
             (SELECT name FROM users WHERE id = t.reporter_id) AS reporter,
             (SELECT id FROM task_priorities WHERE id = t.priority_id) AS priority,
             (SELECT value FROM task_priorities WHERE id = t.priority_id) AS priority_value,
             total_minutes,
             (SELECT SUM(time_spent) FROM task_work_log WHERE task_id = t.id) AS total_minutes_spent,
             created_at,
             updated_at,
             completed_at,
             start_date,
             billable,
             schedule_id,
             END_DATE ${customColumnsQuery} ${statusesQuery}
      FROM tasks t
      WHERE ${filters} ${searchQuery}
      ORDER BY ${sortFields}
    `;
  }

  public static async getGroups(groupBy: string, projectId: string): Promise<ITaskGroup[]> {
    let q = "";
    let params: any[] = [];
    switch (groupBy) {
      case GroupBy.STATUS:
        q = `
          SELECT id,
                 name,
                 (SELECT color_code FROM sys_task_status_categories WHERE id = task_statuses.category_id),
                 (SELECT color_code_dark FROM sys_task_status_categories WHERE id = task_statuses.category_id),
                 category_id
          FROM task_statuses
          WHERE project_id = $1
          ORDER BY sort_order;
        `;
        params = [projectId];
        break;
      case GroupBy.PRIORITY:
        q = `SELECT id, name, color_code, color_code_dark
             FROM task_priorities
             ORDER BY value DESC;`;
        break;
      case GroupBy.LABELS:
        q = `
          SELECT id, name, color_code
          FROM team_labels
          WHERE team_id = $2
            AND EXISTS(SELECT 1
                       FROM tasks
                       WHERE project_id = $1
                         AND EXISTS(SELECT 1 FROM task_labels WHERE task_id = tasks.id AND label_id = team_labels.id))
          ORDER BY name;
        `;
        break;
      case GroupBy.PHASE:
        q = `
          SELECT id, name, color_code, color_code AS color_code_dark, start_date, end_date, sort_index
          FROM project_phases
          WHERE project_id = $1
          ORDER BY sort_index DESC;
        `;
        params = [projectId];
        break;

      default:
        break;
    }

    const result = await db.query(q, params);
    return result.rows;
  }

  @HandleExceptions()
  public static async getList(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const startTime = performance.now();
    console.log(`[PERFORMANCE] getList method called for project ${req.params.id} - THIS METHOD IS DEPRECATED, USE getTasksV3 INSTEAD`);
    
    // PERFORMANCE OPTIMIZATION: Skip expensive progress calculation by default
    // Progress values are already calculated and stored in the database
    // Only refresh if explicitly requested via refresh_progress=true query parameter
    if (req.query.refresh_progress === "true" && req.params.id) {
      console.log(`[PERFORMANCE] Starting progress refresh for project ${req.params.id} (getList)`);
      const progressStartTime = performance.now();
      await this.refreshProjectTaskProgressValues(req.params.id);
      const progressEndTime = performance.now();
      console.log(`[PERFORMANCE] Progress refresh completed in ${(progressEndTime - progressStartTime).toFixed(2)}ms`);
    }

    const isSubTasks = !!req.query.parent_task;
    const groupBy = (req.query.group || GroupBy.STATUS) as string;
    
    // Add customColumns flag to query params
    req.query.customColumns = "true";

    const q = TasksControllerV2.getQuery(req.user?.id as string, req.query);
    const params = isSubTasks ? [req.params.id || null, req.query.parent_task] : [req.params.id || null];

    const result = await db.query(q, params);
    const tasks = [...result.rows];

    const groups = await this.getGroups(groupBy, req.params.id);
    const map = groups.reduce((g: { [x: string]: ITaskGroup }, group) => {
      if (group.id)
        g[group.id] = new TaskListGroup(group);
      return g;
    }, {});

    await this.updateMapByGroup(tasks, groupBy, map);

    const updatedGroups = Object.keys(map).map(key => {
      const group = map[key];

      TasksControllerV2.updateTaskProgresses(group);

      // if (groupBy === GroupBy.PHASE)
      //   group.color_code = group.color_code + TASK_PRIORITY_COLOR_ALPHA;

      return {
        id: key,
        ...group
      };
    });

    const endTime = performance.now();
    const totalTime = endTime - startTime;
    console.log(`[PERFORMANCE] getList method completed in ${totalTime.toFixed(2)}ms for project ${req.params.id} with ${tasks.length} tasks`);
    
    // Log warning if this deprecated method is taking too long
    if (totalTime > 1000) {
      console.warn(`[PERFORMANCE WARNING] DEPRECATED getList method taking ${totalTime.toFixed(2)}ms - Frontend should use getTasksV3 instead!`);
    }

    return res.status(200).send(new ServerResponse(true, updatedGroups));
  }

  public static async updateMapByGroup(tasks: any[], groupBy: string, map: { [p: string]: ITaskGroup }) {
    let index = 0;
    const unmapped = [];
    
    // PERFORMANCE OPTIMIZATION: Remove expensive individual DB calls for each task
    // Progress values are already calculated and included in the main query
    // No need to make additional database calls here
    
    // Process tasks with their already-calculated progress values
    for (const task of tasks) {
      task.index = index++;
      TasksControllerV2.updateTaskViewModel(task);
      
      if (groupBy === GroupBy.STATUS) {
        map[task.status]?.tasks.push(task);
      } else if (groupBy === GroupBy.PRIORITY) {
        map[task.priority]?.tasks.push(task);
      } else if (groupBy === GroupBy.PHASE && task.phase_id) {
        map[task.phase_id]?.tasks.push(task);
      } else {
        unmapped.push(task);
      }
    }

    if (unmapped.length) {
      map[UNMAPPED] = {
        name: UNMAPPED,
        category_id: null,
        color_code: "#fbc84c69",
        tasks: unmapped
      };
    }
  }

  public static updateTaskProgresses(group: ITaskGroup) {
    const todoCount = group.tasks.filter(t => t.status_category?.is_todo).length;
    const doingCount = group.tasks.filter(t => t.status_category?.is_doing).length;
    const doneCount = group.tasks.filter(t => t.status_category?.is_done).length;

    const total = group.tasks.length;

    group.todo_progress = +this.calculateTaskCompleteRatio(todoCount, total);
    group.doing_progress = +this.calculateTaskCompleteRatio(doingCount, total);
    group.done_progress = +this.calculateTaskCompleteRatio(doneCount, total);
  }

  @HandleExceptions()
  public static async getTasksOnly(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const startTime = performance.now();
    console.log(`[PERFORMANCE] getTasksOnly method called for project ${req.params.id} - Consider using getTasksV3 for better performance`);
    
    // PERFORMANCE OPTIMIZATION: Skip expensive progress calculation by default
    // Progress values are already calculated and stored in the database
    // Only refresh if explicitly requested via refresh_progress=true query parameter
    if (req.query.refresh_progress === "true" && req.params.id) {
      console.log(`[PERFORMANCE] Starting progress refresh for project ${req.params.id} (getTasksOnly)`);
      const progressStartTime = performance.now();
      await this.refreshProjectTaskProgressValues(req.params.id);
      const progressEndTime = performance.now();
      console.log(`[PERFORMANCE] Progress refresh completed in ${(progressEndTime - progressStartTime).toFixed(2)}ms`);
    }

    const isSubTasks = !!req.query.parent_task;
      
    // Add customColumns flag to query params
    req.query.customColumns = "true";
    
    const q = TasksControllerV2.getQuery(req.user?.id as string, req.query);
    const params = isSubTasks ? [req.params.id || null, req.query.parent_task] : [req.params.id || null];
    const result = await db.query(q, params);

    let data: any[] = [];

    // if true, we only return the record count
    if (this.isCountsOnly(req.query)) {
      [data] = result.rows;
    } else { // else we return a flat list of tasks
      data = [...result.rows];
      
      // PERFORMANCE OPTIMIZATION: Remove expensive individual DB calls for each task
      // Progress values are already calculated and included in the main query via get_task_complete_ratio
      // The database query already includes complete_ratio, so no need for additional calls
      
      for (const task of data) {
        TasksControllerV2.updateTaskViewModel(task);
      }
    }

    const endTime = performance.now();
    const totalTime = endTime - startTime;
    console.log(`[PERFORMANCE] getTasksOnly method completed in ${totalTime.toFixed(2)}ms for project ${req.params.id} with ${data.length} tasks`);
    
    // Log warning if this method is taking too long
    if (totalTime > 1000) {
      console.warn(`[PERFORMANCE WARNING] getTasksOnly method taking ${totalTime.toFixed(2)}ms - Consider using getTasksV3 for better performance!`);
    }

    return res.status(200).send(new ServerResponse(true, data));
  }

  @HandleExceptions()
  public static async convertToTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const q = `
      UPDATE tasks
      SET parent_task_id = NULL,
          sort_order     = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = $2), 0)
      WHERE id = $1;
    `;
    await db.query(q, [req.body.id, req.body.project_id]);

    const result = await db.query("SELECT get_single_task($1) AS task;", [req.body.id]);
    const [data] = result.rows;
    const model = TasksControllerV2.updateTaskViewModel(data.task);
    return res.status(200).send(new ServerResponse(true, model));
  }

  @HandleExceptions()
  public static async getNewKanbanTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const { id } = req.params;
    const result = await db.query("SELECT get_single_task($1) AS task;", [id]);
    const [data] = result.rows;
    const task = TasksControllerV2.updateTaskViewModel(data.task);
    return res.status(200).send(new ServerResponse(true, task));
  }

  @HandleExceptions()
  public static async resetParentTaskManualProgress(parentTaskId: string): Promise<void> {
    try {
      // Check if this task has subtasks
      const subTasksResult = await db.query(
        "SELECT COUNT(*) as subtask_count FROM tasks WHERE parent_task_id = $1 AND archived IS FALSE",
        [parentTaskId]
      );
      
      const subtaskCount = parseInt(subTasksResult.rows[0]?.subtask_count || "0");
      
      // If it has subtasks, reset the manual_progress flag to false
      if (subtaskCount > 0) {
        await db.query(
          "UPDATE tasks SET manual_progress = false WHERE id = $1",
          [parentTaskId]
        );
        console.log(`Reset manual progress for parent task ${parentTaskId} with ${subtaskCount} subtasks`);
        
        // Get the project settings to determine which calculation method to use
        const projectResult = await db.query(
          "SELECT project_id FROM tasks WHERE id = $1",
          [parentTaskId]
        );
        
        const projectId = projectResult.rows[0]?.project_id;
        
        if (projectId) {
          // Recalculate the parent task's progress based on its subtasks
          const progressResult = await db.query(
            "SELECT get_task_complete_ratio($1) AS ratio",
            [parentTaskId]
          );
          
          const progressRatio = progressResult.rows[0]?.ratio?.ratio || 0;
          
          // Emit the updated progress value to all clients
          // Note: We don't have socket context here, so we can't directly emit
          // This will be picked up on the next client refresh
          console.log(`Recalculated progress for parent task ${parentTaskId}: ${progressRatio}%`);
        }
      }
    } catch (error) {
      log_error(`Error resetting parent task manual progress: ${error}`);
    }
  }

  @HandleExceptions()
  public static async convertToSubtask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {

    const groupType = req.body.group_by;
    let q = ``;

    if (groupType == "status") {
      q = `
        UPDATE tasks
        SET parent_task_id = $3,
            sort_order     = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = $2), 0),
            status_id      = $4
        WHERE id = $1;
      `;
    } else if (groupType == "priority") {
      q = `
        UPDATE tasks
        SET parent_task_id = $3,
            sort_order     = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = $2), 0),
            priority_id    = $4
        WHERE id = $1;
      `;
    } else if (groupType === "phase") {
      await db.query(`
        UPDATE tasks
        SET parent_task_id = $3,
            sort_order     = COALESCE((SELECT MAX(sort_order) + 1 FROM tasks WHERE project_id = $2), 0)
        WHERE id = $1;
      `, [req.body.id, req.body.project_id, req.body.parent_task_id]);
      q = `SELECT handle_on_task_phase_change($1, $2);`;
    }

    if (req.body.to_group_id === UNMAPPED)
      req.body.to_group_id = null;

    const params = groupType === "phase"
      ? [req.body.id, req.body.to_group_id]
      : [req.body.id, req.body.project_id, req.body.parent_task_id, req.body.to_group_id];
    await db.query(q, params);
    
    // Reset the parent task's manual progress when converting a task to a subtask
    if (req.body.parent_task_id) {
      await this.resetParentTaskManualProgress(req.body.parent_task_id);
    }

    const result = await db.query("SELECT get_single_task($1) AS task;", [req.body.id]);
    const [data] = result.rows;
    const model = TasksControllerV2.updateTaskViewModel(data.task);
    return res.status(200).send(new ServerResponse(true, model));
  }

  public static async getTaskSubscribers(taskId: string) {
    const q = `
      SELECT u.name, u.avatar_url, ts.user_id, ts.team_member_id, ts.task_id
      FROM task_subscribers ts
             LEFT JOIN users u ON ts.user_id = u.id
      WHERE ts.task_id = $1;
    `;
    const result = await db.query(q, [taskId]);

    for (const member of result.rows)
      member.color_code = getColor(member.name);

    return this.createTagList(result.rows);
  }

  public static async getProjectSubscribers(projectId: string) {
    const q = `
      SELECT u.name, u.avatar_url, ps.user_id, ps.team_member_id, ps.project_id
      FROM project_subscribers ps
             LEFT JOIN users u ON ps.user_id = u.id
      WHERE ps.project_id = $1;
    `;
    const result = await db.query(q, [projectId]);

    for (const member of result.rows)
      member.color_code = getColor(member.name);

    return this.createTagList(result.rows);
  }

  public static async checkUserAssignedToTask(taskId: string, userId: string, teamId: string) {
    const q = `
    SELECT EXISTS(
        SELECT * FROM tasks_assignees WHERE task_id = $1 AND team_member_id = (SELECT team_member_id FROM team_member_info_view WHERE user_id = $2 AND team_id = $3)
    );
    `;
    const result = await db.query(q, [taskId, userId, teamId]);
    const [data] = result.rows;

    return data.exists;

  }

  public static async getTasksByName(searchString: string, projectId: string, taskId: string) {
    const q = `SELECT id AS value ,
       name AS label,
       CONCAT((SELECT key FROM projects WHERE id = t.project_id), '-', task_no) AS task_key
      FROM tasks t
      WHERE t.name ILIKE '%${searchString}%'
        AND t.project_id = $1 AND t.id != $2
      LIMIT 15;`;
    const result = await db.query(q, [projectId, taskId]);

    return result.rows;
  }

  @HandleExceptions()
  public static async getSubscribers(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const subscribers = await this.getTaskSubscribers(req.params.id);
    return res.status(200).send(new ServerResponse(true, subscribers));
  }

  @HandleExceptions()
  public static async searchTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const { projectId, taskId, searchQuery } = req.query;
    const tasks = await this.getTasksByName(searchQuery as string, projectId as string, taskId as string);
    return res.status(200).send(new ServerResponse(true, tasks));
  }

  @HandleExceptions()
  public static async getTaskDependencyStatus(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const { statusId, taskId } = req.query;
    const canContinue = await TasksControllerV2.checkForCompletedDependencies(taskId as string, statusId as string);
    return res.status(200).send(new ServerResponse(true, { can_continue: canContinue }));
  }

  @HandleExceptions()
  public static async checkForCompletedDependencies(taskId: string, nextStatusId: string): Promise<IWorkLenzResponse> {
    const q = `SELECT
    CASE
        WHEN EXISTS (
            -- Check if the status id is not in the "done" category
            SELECT 1
            FROM task_statuses ts
            WHERE ts.id = $2
              AND ts.project_id = (SELECT project_id FROM tasks WHERE id = $1)
              AND ts.category_id IN (
                  SELECT id FROM sys_task_status_categories WHERE is_done IS FALSE
              )
        ) THEN TRUE -- If status is not in the "done" category, continue immediately (TRUE)

        WHEN EXISTS (
            -- Check if any dependent tasks are not completed
            SELECT 1
            FROM task_dependencies td
            LEFT JOIN public.tasks t ON t.id = td.related_task_id
            WHERE td.task_id = $1
              AND t.status_id NOT IN (
                  SELECT id
                  FROM task_statuses ts
                  WHERE t.project_id = ts.project_id
                    AND ts.category_id IN (
                        SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE
                    )
              )
        ) THEN FALSE -- If there are incomplete dependent tasks, do not continue (FALSE)

        ELSE TRUE -- Continue if no other conditions block the process
    END AS can_continue;`;
    const result = await db.query(q, [taskId, nextStatusId]);
    const [data] = result.rows;

    return data.can_continue;
  }

  public static async getTaskStatusColor(status_id: string) {
    try {
      const q = `SELECT color_code, color_code_dark
      FROM sys_task_status_categories
      WHERE id = (SELECT category_id FROM task_statuses WHERE id = $1)`;
      const result = await db.query(q, [status_id]);
      const [data] = result.rows;
      return data;
    } catch (e) {
      log_error(e);
    }
  }

  @HandleExceptions()
  public static async assignLabelsToTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const { id } = req.params;
    const { labels }: { labels: string[] } = req.body;

    labels.forEach(async (label: string) => {
      const q = `SELECT add_or_remove_task_label($1, $2) AS labels;`;
      await db.query(q, [id, label]);
    });
    return res.status(200).send(new ServerResponse(true, null, "Labels assigned successfully"));
  }

  /**
   * Updates a custom column value for a task
   * @param req The request object
   * @param res The response object
   */
  @HandleExceptions()
  public static async updateCustomColumnValue(
    req: IWorkLenzRequest,
    res: IWorkLenzResponse
  ): Promise<IWorkLenzResponse> {
    const { taskId } = req.params;
    const { column_key, value, project_id } = req.body;

    if (!taskId || !column_key || value === undefined || !project_id) {
      return res.status(400).send(new ServerResponse(false, "Missing required parameters"));
    }

    // Get column information
    const columnQuery = `
      SELECT id, field_type 
      FROM cc_custom_columns 
      WHERE project_id = $1 AND key = $2
    `;
    const columnResult = await db.query(columnQuery, [project_id, column_key]);
    
    if (columnResult.rowCount === 0) {
      return res.status(404).send(new ServerResponse(false, "Custom column not found"));
    }
    
    const column = columnResult.rows[0];
    const columnId = column.id;
    const fieldType = column.field_type;
    
    // Determine which value field to use based on the field_type
    let textValue = null;
    let numberValue = null;
    let dateValue = null;
    let booleanValue = null;
    let jsonValue = null;
    
    switch (fieldType) {
      case "number":
        numberValue = parseFloat(String(value));
        break;
      case "date":
        dateValue = new Date(String(value));
        break;
      case "checkbox":
        booleanValue = Boolean(value);
        break;
      case "people":
        jsonValue = JSON.stringify(Array.isArray(value) ? value : [value]);
        break;
      default:
        textValue = String(value);
    }
    
    // Check if a value already exists
    const existingValueQuery = `
      SELECT id 
      FROM cc_column_values 
      WHERE task_id = $1 AND column_id = $2
    `;
    const existingValueResult = await db.query(existingValueQuery, [taskId, columnId]);
    
    if (existingValueResult.rowCount && existingValueResult.rowCount > 0) {
      // Update existing value
      const updateQuery = `
        UPDATE cc_column_values 
        SET text_value = $1, 
            number_value = $2, 
            date_value = $3, 
            boolean_value = $4, 
            json_value = $5, 
            updated_at = NOW() 
        WHERE task_id = $6 AND column_id = $7
      `;
      await db.query(updateQuery, [
        textValue, 
        numberValue, 
        dateValue, 
        booleanValue, 
        jsonValue, 
        taskId, 
        columnId
      ]);
    } else {
      // Insert new value
      const insertQuery = `
        INSERT INTO cc_column_values 
        (task_id, column_id, text_value, number_value, date_value, boolean_value, json_value, created_at, updated_at) 
        VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
      `;
      await db.query(insertQuery, [
        taskId, 
        columnId, 
        textValue, 
        numberValue, 
        dateValue, 
        booleanValue, 
        jsonValue
      ]);
    }

    return res.status(200).send(new ServerResponse(true, { 
      task_id: taskId,
      column_key,
      value
    }));
  }

  public static async refreshProjectTaskProgressValues(projectId: string): Promise<void> {
    try {     
      // Run the recalculate_all_task_progress function only for tasks in this project
      const query = `
      DO $$
      BEGIN
        -- First, reset manual_progress flag for all tasks that have subtasks within this project
        UPDATE tasks AS t
        SET manual_progress = FALSE
        WHERE project_id = '${projectId}'
        AND EXISTS (
            SELECT 1
            FROM tasks
            WHERE parent_task_id = t.id
            AND archived IS FALSE
        );
        
        -- Start recalculation from leaf tasks (no subtasks) and propagate upward
        -- This ensures calculations are done in the right order
        WITH RECURSIVE task_hierarchy AS (
            -- Base case: Start with all leaf tasks (no subtasks) in this project
            SELECT 
                id,
                parent_task_id,
                0 AS level
            FROM tasks
            WHERE project_id = '${projectId}'
            AND NOT EXISTS (
                SELECT 1 FROM tasks AS sub
                WHERE sub.parent_task_id = tasks.id
                AND sub.archived IS FALSE
            )
            AND archived IS FALSE
            
            UNION ALL
            
            -- Recursive case: Move up to parent tasks, but only after processing all their children
            SELECT 
                t.id,
                t.parent_task_id,
                th.level + 1
            FROM tasks t
            JOIN task_hierarchy th ON t.id = th.parent_task_id
            WHERE t.archived IS FALSE
        )
        -- Sort by level to ensure we calculate in the right order (leaves first, then parents)
        UPDATE tasks
        SET progress_value = (SELECT (get_task_complete_ratio(tasks.id)->>'ratio')::FLOAT)
        FROM (
            SELECT id, level
            FROM task_hierarchy
            ORDER BY level
        ) AS ordered_tasks
        WHERE tasks.id = ordered_tasks.id
        AND tasks.project_id = '${projectId}'
        AND (manual_progress IS FALSE OR manual_progress IS NULL);
      END $$;
      `;
      
      await db.query(query);
      console.log(`Finished refreshing progress values for project ${projectId}`);
    } catch (error) {
      log_error("Error refreshing project task progress values", error);
    }
  }

  public static async updateTaskProgress(taskId: string): Promise<void> {
    try {
      // Calculate the task's progress using get_task_complete_ratio
      const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]);
      const [data] = result.rows;
      
      if (data && data.info && data.info.ratio !== undefined) {
        const progressValue = +((data.info.ratio || 0).toFixed());
        
        // Update the task's progress_value in the database
        await db.query(
          "UPDATE tasks SET progress_value = $1 WHERE id = $2",
          [progressValue, taskId]
        );
        
        console.log(`Updated progress for task ${taskId} to ${progressValue}%`);
        
        // If this task has a parent, update the parent's progress as well
        const parentResult = await db.query(
          "SELECT parent_task_id FROM tasks WHERE id = $1",
          [taskId]
        );
        
        if (parentResult.rows.length > 0 && parentResult.rows[0].parent_task_id) {
          await this.updateTaskProgress(parentResult.rows[0].parent_task_id);
        }
      }
    } catch (error) {
      log_error(`Error updating task progress: ${error}`);
    }
  }

  // Add this method to update progress when a task's weight is changed
  public static async updateTaskWeight(taskId: string, weight: number): Promise<void> {
    try {
      // Update the task's weight
      await db.query(
        "UPDATE tasks SET weight = $1 WHERE id = $2",
        [weight, taskId]
      );
      
      // Get the parent task ID
      const parentResult = await db.query(
        "SELECT parent_task_id FROM tasks WHERE id = $1",
        [taskId]
      );
      
      // If this task has a parent, update the parent's progress
      if (parentResult.rows.length > 0 && parentResult.rows[0].parent_task_id) {
        await this.updateTaskProgress(parentResult.rows[0].parent_task_id);
      }
    } catch (error) {
      log_error(`Error updating task weight: ${error}`);
    }
  }

  @HandleExceptions()
  public static async getTasksV3(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    const startTime = performance.now();
    const isSubTasks = !!req.query.parent_task;
    const groupBy = (req.query.group || GroupBy.STATUS) as string;
    const archived = req.query.archived === "true";

    // PERFORMANCE OPTIMIZATION: Skip expensive progress calculation by default
    // Progress values are already calculated and stored in the database
    // Only refresh if explicitly requested via refresh_progress=true query parameter
    // This dramatically improves initial load performance (from ~2-5s to ~200-500ms)
    const shouldRefreshProgress = req.query.refresh_progress === "true";
    
    if (shouldRefreshProgress && req.params.id) {
      const progressStartTime = performance.now();
      await this.refreshProjectTaskProgressValues(req.params.id);
      const progressEndTime = performance.now();
    }

    const queryStartTime = performance.now();
    const q = TasksControllerV2.getQuery(req.user?.id as string, req.query);
    const params = isSubTasks ? [req.params.id || null, req.query.parent_task] : [req.params.id || null];

    const result = await db.query(q, params);
    const tasks = [...result.rows];
    const queryEndTime = performance.now();

    // Get groups metadata dynamically from database
    const groupsStartTime = performance.now();
    const groups = await this.getGroups(groupBy, req.params.id);
    const groupsEndTime = performance.now();

    // Create priority value to name mapping
    const priorityMap: Record<string, string> = {
      "0": "low",
      "1": "medium", 
      "2": "high"
    };

    // Create status category mapping based on actual status names from database
    const statusCategoryMap: Record<string, string> = {};
    for (const group of groups) {
      if (groupBy === GroupBy.STATUS && group.id) {
        // Use the actual status name from database, convert to lowercase for consistency
        statusCategoryMap[group.id] = group.name.toLowerCase().replace(/\s+/g, "_");
      }
    }

    // Transform tasks with all necessary data preprocessing
    const transformStartTime = performance.now();
    const transformedTasks = tasks.map((task, index) => {
      // Update task with calculated values (lightweight version)
      TasksControllerV2.updateTaskViewModel(task);
      task.index = index;

      // Convert time values
      const convertTimeValue = (value: any): number => {
        if (typeof value === "number") return value;
        if (typeof value === "string") {
          const parsed = parseFloat(value);
          return isNaN(parsed) ? 0 : parsed;
        }
        if (value && typeof value === "object") {
          if ("hours" in value || "minutes" in value) {
            const hours = Number(value.hours || 0);
            const minutes = Number(value.minutes || 0);
            return hours + (minutes / 60);
          }
        }
        return 0;
      };

      return {
        id: task.id,
        task_key: task.task_key || "",
        title: task.name || "",
        description: task.description || "",
        // Use dynamic status mapping from database
        status: statusCategoryMap[task.status] || task.status,
        // Pre-processed priority using mapping  
        priority: priorityMap[task.priority_value?.toString()] || "medium",
        // Use actual phase name from database
        phase: task.phase_name || "Development",
        progress: typeof task.complete_ratio === "number" ? task.complete_ratio : 0,
        assignees: task.assignees?.map((a: any) => a.team_member_id) || [],
        assignee_names: task.assignee_names || task.names || [],
        labels: task.labels?.map((l: any) => ({
          id: l.id || l.label_id,
          name: l.name,
          color: l.color_code || "#1890ff",
          end: l.end,
          names: l.names
        })) || [],
        dueDate: task.end_date,
        timeTracking: {
          estimated: convertTimeValue(task.total_time),
          logged: convertTimeValue(task.time_spent),
        },
        customFields: {},
        createdAt: task.created_at || new Date().toISOString(),
        updatedAt: task.updated_at || new Date().toISOString(),
        order: typeof task.sort_order === "number" ? task.sort_order : 0,
        // Additional metadata for frontend
        originalStatusId: task.status,
        originalPriorityId: task.priority,
        statusColor: task.status_color,
        priorityColor: task.priority_color,
        // Add subtask count
        sub_tasks_count: task.sub_tasks_count || 0,
        // Add indicator fields for frontend icons
        comments_count: task.comments_count || 0,
        has_subscribers: !!task.has_subscribers,
        attachments_count: task.attachments_count || 0,
        has_dependencies: !!task.has_dependencies,
        schedule_id: task.schedule_id || null,
      };
    });
    const transformEndTime = performance.now();

    // Create groups based on dynamic data from database
    const groupingStartTime = performance.now();
    const groupedResponse: Record<string, any> = {};
    
    // Initialize groups from database data
    groups.forEach(group => {
      const groupKey = groupBy === GroupBy.STATUS 
        ? group.name.toLowerCase().replace(/\s+/g, "_")
        : groupBy === GroupBy.PRIORITY
        ? priorityMap[(group as any).value?.toString()] || group.name.toLowerCase()
        : group.name.toLowerCase().replace(/\s+/g, "_");
      
      groupedResponse[groupKey] = {
        id: group.id,
        title: group.name,
        groupType: groupBy,
        groupValue: groupKey,
        collapsed: false,
        tasks: [],
        taskIds: [],
        color: group.color_code || this.getDefaultGroupColor(groupBy, groupKey),
        // Include additional metadata from database
        category_id: group.category_id,
        start_date: group.start_date,
        end_date: group.end_date,
        sort_index: (group as any).sort_index,
      };
    });

    // Distribute tasks into groups
    transformedTasks.forEach(task => {
      let groupKey: string;
      if (groupBy === GroupBy.STATUS) {
        groupKey = task.status;
      } else if (groupBy === GroupBy.PRIORITY) {
        groupKey = task.priority;
      } else {
        groupKey = task.phase.toLowerCase().replace(/\s+/g, "_");
      }

      if (groupedResponse[groupKey]) {
        groupedResponse[groupKey].tasks.push(task);
        groupedResponse[groupKey].taskIds.push(task.id);
      }
    });

    // Sort tasks within each group by order
    Object.values(groupedResponse).forEach((group: any) => {
      group.tasks.sort((a: any, b: any) => a.order - b.order);
    });

    // Convert to array format expected by frontend, maintaining database order
    const responseGroups = groups
      .map(group => {
        const groupKey = groupBy === GroupBy.STATUS 
          ? group.name.toLowerCase().replace(/\s+/g, "_")
          : groupBy === GroupBy.PRIORITY
          ? priorityMap[(group as any).value?.toString()] || group.name.toLowerCase()
          : group.name.toLowerCase().replace(/\s+/g, "_");
        
        return groupedResponse[groupKey];
      })
      .filter(group => group && (group.tasks.length > 0 || req.query.include_empty === "true"));
    
    const groupingEndTime = performance.now();

    const endTime = performance.now();
    const totalTime = endTime - startTime;
    
    // Log warning if request is taking too long
    if (totalTime > 1000) {
      console.warn(`[PERFORMANCE WARNING] Slow request detected: ${totalTime.toFixed(2)}ms for project ${req.params.id} with ${transformedTasks.length} tasks`);
    }

    return res.status(200).send(new ServerResponse(true, {
      groups: responseGroups,
      allTasks: transformedTasks,
      grouping: groupBy,
      totalTasks: transformedTasks.length,
      performanceMetrics: {
        totalTime: Math.round(totalTime),
        queryTime: Math.round(queryEndTime - queryStartTime),
        transformTime: Math.round(transformEndTime - transformStartTime),
        groupingTime: Math.round(groupingEndTime - groupingStartTime),
        progressRefreshTime: shouldRefreshProgress ? Math.round(queryStartTime - startTime) : 0,
        taskCount: transformedTasks.length
      }
    }));
  }

  private static getDefaultGroupColor(groupBy: string, groupValue: string): string {
    const colorMaps: Record<string, Record<string, string>> = {
      [GroupBy.STATUS]: {
        todo: "#f0f0f0",
        doing: "#1890ff", 
        done: "#52c41a",
      },
      [GroupBy.PRIORITY]: {
        critical: "#ff4d4f",
        high: "#ff7a45",
        medium: "#faad14",
        low: "#52c41a",
      },
      [GroupBy.PHASE]: {
        planning: "#722ed1",
        development: "#1890ff",
        testing: "#faad14",
        deployment: "#52c41a",
      },
    };
    
    return colorMaps[groupBy]?.[groupValue] || "#d9d9d9";
  }

  @HandleExceptions()
  public static async refreshTaskProgress(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    try {
      const startTime = performance.now();
      
      if (req.params.id) {
        console.log(`[PERFORMANCE] Starting background progress refresh for project ${req.params.id}`);
        await this.refreshProjectTaskProgressValues(req.params.id);
        
        const endTime = performance.now();
        const totalTime = endTime - startTime;
        console.log(`[PERFORMANCE] Background progress refresh completed in ${totalTime.toFixed(2)}ms for project ${req.params.id}`);
        
        return res.status(200).send(new ServerResponse(true, {
          message: "Task progress values refreshed successfully",
          performanceMetrics: {
            refreshTime: Math.round(totalTime),
            projectId: req.params.id
          }
        }));
      }
      return res.status(400).send(new ServerResponse(false, null, "Project ID is required"));
    } catch (error) {
      console.error("Error refreshing task progress:", error);
      return res.status(500).send(new ServerResponse(false, null, "Failed to refresh task progress"));
    }
  }

  // Optimized method for getting task progress without blocking main UI
  @HandleExceptions()
  public static async getTaskProgressStatus(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
    try {
      if (!req.params.id) {
        return res.status(400).send(new ServerResponse(false, null, "Project ID is required"));
      }

      // Get basic progress stats without expensive calculations
      const result = await db.query(`
        SELECT 
          COUNT(*) as total_tasks,
          COUNT(CASE WHEN EXISTS(
            SELECT 1 FROM tasks_with_status_view 
            WHERE tasks_with_status_view.task_id = tasks.id 
            AND is_done IS TRUE
          ) THEN 1 END) as completed_tasks,
          AVG(CASE 
            WHEN progress_value IS NOT NULL THEN progress_value 
            ELSE 0 
          END) as avg_progress,
          MAX(updated_at) as last_updated
        FROM tasks 
        WHERE project_id = $1 AND archived IS FALSE
      `, [req.params.id]);

      const [stats] = result.rows;
      
      return res.status(200).send(new ServerResponse(true, {
        projectId: req.params.id,
        totalTasks: parseInt(stats.total_tasks) || 0,
        completedTasks: parseInt(stats.completed_tasks) || 0,
        avgProgress: parseFloat(stats.avg_progress) || 0,
        lastUpdated: stats.last_updated,
        completionPercentage: stats.total_tasks > 0 ? 
          Math.round((parseInt(stats.completed_tasks) / parseInt(stats.total_tasks)) * 100) : 0
      }));
    } catch (error) {
      console.error("Error getting task progress status:", error);
      return res.status(500).send(new ServerResponse(false, null, "Failed to get task progress status"));
    }
  }
}
