// src/server/features/tasks/tasks.service.ts
// Business logic for tasks management

import { db } from '../../shared/db/client';
import { tasks } from './db/tasks.table';
import { eq, and, isNull } from 'drizzle-orm';
import { loggerService } from '../../shared/lib/logger';
import { AppError, ErrorCode } from '../../shared/lib/errors';

// Initialize logger for tasks operations (DB channel for database operations)
const tasksLogger = loggerService.get('DB').child({ module: 'tasks' });

// Position type for grid layout
export interface Position {
  x: number;
  y: number;
  w: number;
  h: number;
}

// Types - matching the database schema
export interface Task {
  id: string;
  userId: string;
  text: string;
  isCompleted: boolean;
  parentId: string | null;
  position: Position | null;
  order: number | null;
  createdAt: string;
  updatedAt: string;
}

// Helper to parse position from JSON string
function parsePosition(positionJson: string | null): Position | null {
  if (!positionJson) return null;
  try {
    return JSON.parse(positionJson) as Position;
  } catch {
    return null;
  }
}

// Get all tasks for a specific user
export async function getTasks(userId: string): Promise<Task[]> {
  const result = await db
    .select()
    .from(tasks)
    .where(and(eq(tasks.userId, userId), isNull(tasks.archivedAt)));
  return result.map((task: any) => ({
    ...task,
    position: parsePosition(task.position),
    createdAt: task.createdAt.toISOString(),
    updatedAt: task.updatedAt.toISOString(),
  })) as Task[];
}

// Get task by ID with ownership check
export async function getTaskById(id: string, userId: string): Promise<Task | null> {
  const result = await db
    .select()
    .from(tasks)
    .where(and(eq(tasks.id, id), eq(tasks.userId, userId), isNull(tasks.archivedAt)))
    .limit(1);
  if (result.length === 0) return null;
  const task = result[0];
  return {
    ...task,
    position: parsePosition(task.position),
    createdAt: task.createdAt.toISOString(),
    updatedAt: task.updatedAt.toISOString(),
  } as Task;
}

// Create task
export async function createTask(
  userId: string,
  data: {
    text: string;
    isCompleted?: boolean;
    parentId?: string | null;
    position?: Position;
    order?: number;
  }
): Promise<Task> {
  const result = await db
    .insert(tasks)
    .values({
      userId,
      text: data.text,
      isCompleted: data.isCompleted ?? false,
      parentId: data.parentId ?? null,
      position: data.position ? JSON.stringify(data.position) : null,
      order: data.order ?? null,
    })
    .$returningId();

  const created = await getTaskById(result[0].id, userId);
  if (!created) {
    throw new Error('Failed to create task');
  }
  return created;
}

// Update task with ownership check
export async function updateTask(
  id: string,
  userId: string,
  data: {
    text?: string;
    isCompleted?: boolean;
    parentId?: string | null;
    position?: Position;
    order?: number;
  }
): Promise<Task | null> {
  // Verify ownership
  const existingTask = await db
    .select({ userId: tasks.userId })
    .from(tasks)
    .where(eq(tasks.id, id))
    .limit(1);

  if (existingTask.length === 0) {
    throw new AppError('Task not found', 404, ErrorCode.NOT_FOUND);
  }

  if (existingTask[0].userId !== userId) {
    throw new AppError('You do not have permission to update this task', 403, ErrorCode.FORBIDDEN);
  }

  const updateData: any = {};
  if (data.text !== undefined) updateData.text = data.text;
  if (data.isCompleted !== undefined) updateData.isCompleted = data.isCompleted;
  if (data.parentId !== undefined) updateData.parentId = data.parentId;
  if (data.position !== undefined) updateData.position = data.position ? JSON.stringify(data.position) : null;
  if (data.order !== undefined) updateData.order = data.order;

  await db.update(tasks).set(updateData).where(eq(tasks.id, id));
  return await getTaskById(id, userId);
}

// Delete task recursively (including all subtasks) with ownership check
export async function deleteTask(id: string, userId: string): Promise<boolean> {
  // Verify ownership
  const existingTask = await db
    .select({ userId: tasks.userId })
    .from(tasks)
    .where(eq(tasks.id, id))
    .limit(1);

  if (existingTask.length === 0) {
    throw new AppError('Task not found', 404, ErrorCode.NOT_FOUND);
  }

  if (existingTask[0].userId !== userId) {
    throw new AppError('You do not have permission to delete this task', 403, ErrorCode.FORBIDDEN);
  }

  // First, find all subtasks recursively
  const subtasks = await findAllSubtasks(id, userId);

  // Delete all subtasks
  for (const subtask of subtasks) {
    await db.delete(tasks).where(eq(tasks.id, subtask.id));
  }

  // Delete the main task
  await db.delete(tasks).where(eq(tasks.id, id));
  return true;
}

// Helper function to find all subtasks recursively
async function findAllSubtasks(parentId: string, userId: string): Promise<Array<{ id: string }>> {
  const directChildren = await db
    .select({ id: tasks.id })
    .from(tasks)
    .where(and(eq(tasks.parentId, parentId), eq(tasks.userId, userId)));

  const allSubtasks: Array<{ id: string }> = [...directChildren];

  for (const child of directChildren) {
    const nestedSubtasks = await findAllSubtasks(child.id, userId);
    allSubtasks.push(...nestedSubtasks);
  }

  return allSubtasks;
}

// Update task positions (batch update for grid layout) with ownership check
export async function updateTaskPositions(
  userId: string,
  tasksPositions: Array<{ id: string; position: Position }>
): Promise<boolean> {
  try {
    await db.transaction(async (tx) => {
      for (const { id, position } of tasksPositions) {
        // Verify ownership before updating
        const existingTask = await tx
          .select({ userId: tasks.userId })
          .from(tasks)
          .where(eq(tasks.id, id))
          .limit(1);

        if (existingTask.length === 0 || existingTask[0].userId !== userId) {
          throw new AppError('You do not have permission to update this task', 403, ErrorCode.FORBIDDEN);
        }

        await tx
          .update(tasks)
          .set({ position: JSON.stringify(position) })
          .where(eq(tasks.id, id));
      }
    });
    return true;
  } catch (error) {
    tasksLogger.error('Error updating task positions:', error);
    if (error instanceof AppError) {
      throw error;
    }
    return false;
  }
}

// Toggle task completion with ownership check
export async function toggleTask(id: string, userId: string): Promise<Task | null> {
  const task = await getTaskById(id, userId);
  if (!task) {
    throw new AppError('Task not found', 404, ErrorCode.NOT_FOUND);
  }

  return await updateTask(id, userId, { isCompleted: !task.isCompleted });
}

// Reorder tasks with hierarchy support (strict 2-level hierarchy) with ownership check
export async function reorderTasks(
  userId: string,
  rootId: string,
  tasksData: Array<{ id: string; order: number; parentId: string | null }>
): Promise<boolean> {
  try {
    await db.transaction(async (tx) => {
      for (const taskData of tasksData) {
        const { id, order, parentId } = taskData;

        // Verify ownership before updating
        const existingTask = await tx
          .select({ userId: tasks.userId })
          .from(tasks)
          .where(eq(tasks.id, id))
          .limit(1);

        if (existingTask.length === 0 || existingTask[0].userId !== userId) {
          throw new AppError('You do not have permission to update this task', 403, ErrorCode.FORBIDDEN);
        }

        // === LOYAL VALIDATION - ONLY RULE 4 ===
        
        // Rule 4: Prevent circular references (parent cannot be a descendant of child)
        if (parentId !== null) {
          // Check if parentId is a descendant of id
          let currentId: string | null = parentId;
          const visited = new Set<string>();

          while (currentId) {
            if (visited.has(currentId)) {
              tasksLogger.error(`[Reorder Error] Circular reference detected involving task ${id}.`);
              throw new Error(`Circular reference detected involving task ${id}.`);
            }
            visited.add(currentId);

            if (currentId === id) {
              tasksLogger.error(`[Reorder Error] Task ${id} cannot be a child of ${parentId} because ${parentId} is its descendant.`);
              throw new Error(
                `Invalid hierarchy: Task ${id} cannot be a child of ${parentId} because ${parentId} is its descendant.`
              );
            }

            const parent = await tx
              .select({ parentId: tasks.parentId })
              .from(tasks)
              .where(eq(tasks.id, currentId))
              .limit(1);

            currentId = parent[0]?.parentId ?? null;
          }
        }

        // Update task with new order and parentId
        await tx
          .update(tasks)
          .set({
            order,
            parentId,
          })
          .where(eq(tasks.id, id));
      }
    });
    return true;
  } catch (error) {
    tasksLogger.error('Error reordering tasks:', error);
    if (error instanceof AppError) {
      throw error;
    }
    throw error; // Re-throw to let the route handler return proper error response
  }
}
