import { Router, Request, Response } from 'express';
import { asyncHandler } from '../utils/asyncHandler';
import { db } from '../db';
import { tasks, subtasks, categories } from '../db/schema';
import { eq, and, desc, asc, gte, lte, inArray, sql } from 'drizzle-orm';
import { authMiddleware } from '../utils/auth';
import { AppError } from '../middleware/errorHandler';
import { taskCreateSchema, taskUpdateSchema, taskQuerySchema, canCompleteTask } from '../utils/validation';
import { z } from 'zod';

const router = Router();

router.use(authMiddleware);

function applyTaskFilters(query: any, userId: string, filters: any) {
  const conditions = [eq(tasks.userId, userId)];

  if (filters.categoryId) {
    conditions.push(eq(tasks.categoryId, filters.categoryId));
  }
  if (filters.completed !== undefined) {
    conditions.push(eq(tasks.completed, filters.completed));
  }
  if (filters.dueBefore) {
    conditions.push(lte(tasks.dueDate, filters.dueBefore));
  }
  if (filters.dueAfter) {
    conditions.push(gte(tasks.dueDate, filters.dueAfter));
  }
  if (filters.priority) {
    conditions.push(eq(tasks.priority, filters.priority));
  }

  return query.where(and(...conditions));
}

function applyTaskSorting(query: any, sortBy: string = 'dueDate', sortOrder: string = 'asc') {
  const orderFn = sortOrder === 'desc' ? desc : asc;
  const columnMap: Record<string, any> = {
    dueDate: tasks.dueDate,
    priority: tasks.priority,
    createdAt: tasks.createdAt,
    title: tasks.title,
  };
  return query.orderBy(orderFn(columnMap[sortBy] || tasks.dueDate));
}

router.get('/', asyncHandler(async (req: Request, res: Response) => {
  const filters = taskQuerySchema.parse(req.query);
  const userId = req.user!.userId;

  let query: any = db.select().from(tasks);
  query = applyTaskFilters(query, userId, filters);
  query = applyTaskSorting(query, filters.sortBy, filters.sortOrder);

  const limit = filters.limit ?? 50;
  const offset = filters.offset ?? 0;
  query = query.limit(limit).offset(offset);

  const results = await query;

  // Fetch subtasks for each task
  const taskIds = results.map((t: { id: string }) => t.id);
  let taskSubtasks: any[] = [];
  if (taskIds.length > 0) {
    taskSubtasks = await db
      .select()
      .from(subtasks)
      .where(and(eq(subtasks.userId, userId), inArray(subtasks.taskId, taskIds)))
      .orderBy(asc(subtasks.order));
  }

  const subtasksByTask = taskSubtasks.reduce((acc, st) => {
    if (!acc[st.taskId]) acc[st.taskId] = [];
    acc[st.taskId].push(st);
    return acc;
  }, {} as Record<string, any[]>);

  const tasksWithSubtasks = results.map((task: any) => ({
    ...task,
    subtasks: subtasksByTask[task.id] || [],
  }));

  // Get total count
  const countQuery: any = db.select({ count: sql`count(*)` }).from(tasks);
  const countResult = await applyTaskFilters(countQuery, userId, filters);
  const total = Number(countResult[0]?.count ?? 0);

  res.json({ tasks: tasksWithSubtasks, total, limit, offset });
}));

router.get('/:id', asyncHandler(async (req: Request, res: Response) => {
  const userId = req.user!.userId;

  const task = await db
    .select()
    .from(tasks)
    .where(and(eq(tasks.id, req.params.id), eq(tasks.userId, userId)))
    .limit(1);

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

  const taskSubtasks = await db
    .select()
    .from(subtasks)
    .where(and(eq(subtasks.taskId, req.params.id), eq(subtasks.userId, userId)))
    .orderBy(asc(subtasks.order));

  res.json({ ...task[0], subtasks: taskSubtasks });
}));

router.post('/', asyncHandler(async (req: Request, res: Response) => {
  const data = taskCreateSchema.parse(req.body);
  const userId = req.user!.userId;
  const now = Date.now();

  // Verify category exists and belongs to user (optional - tasks may be uncategorized)
  if (data.categoryId) {
    const cat = await db
      .select()
      .from(categories)
      .where(and(eq(categories.id, data.categoryId), eq(categories.userId, userId)))
      .limit(1);

    if (cat.length === 0) {
      throw new AppError('NOT_FOUND', 'Category not found', 404);
    }
  }

  const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
  
  const newTask = {
    id: taskId,
    userId,
    categoryId: data.categoryId,
    tags: data.tags ?? '',
    title: data.title,
    description: data.description ?? '',
    priority: data.priority ?? 'none',
    completed: false,
    dueDate: data.dueDate ?? 0,
    dueTime: data.dueTime ?? '',
    endTime: data.endTime ?? '',
    allDay: data.allDay ?? false,
    assigneeId: data.assigneeId ?? null,
    reminder: data.reminder ?? 'none',
    reminders: data.reminders ?? '',
    createdAt: now,
    updatedAt: now,
  };

  await db.insert(tasks).values(newTask);

  // Create subtasks if provided
  if (data.subtasks && data.subtasks.length > 0) {
    const subtaskValues = data.subtasks.map((st, index) => ({
      id: `sub_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}${index}`,
      userId,
      taskId,
      title: st.title,
      categoryId: (st as any).categoryId ?? null,
      completed: false,
      order: index,
      createdAt: now,
      updatedAt: now,
    }));
    await db.insert(subtasks).values(subtaskValues);
  }

  const createdSubtasks = await db
    .select()
    .from(subtasks)
    .where(and(eq(subtasks.taskId, taskId), eq(subtasks.userId, userId)))
    .orderBy(asc(subtasks.order));

  res.status(201).json({ ...newTask, subtasks: createdSubtasks });
}));

router.patch('/:id', asyncHandler(async (req: Request, res: Response) => {
  const data = taskUpdateSchema.parse(req.body);
  const userId = req.user!.userId;

  const existing = await db
    .select()
    .from(tasks)
    .where(and(eq(tasks.id, req.params.id), eq(tasks.userId, userId)))
    .limit(1);

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

  // Verify category if provided
  if (data.categoryId) {
    const cat = await db
      .select()
      .from(categories)
      .where(and(eq(categories.id, data.categoryId), eq(categories.userId, userId)))
      .limit(1);
    if (cat.length === 0) {
      throw new AppError('NOT_FOUND', 'Category not found', 404);
    }
  }

  // Prevent completing tasks with future due dates
  if (data.completed === true) {
    const effectiveDueDate = data.dueDate ?? existing[0].dueDate;
    if (!canCompleteTask(effectiveDueDate)) {
      throw new AppError('VALIDATION_ERROR', 'Cannot mark a task as completed if its due date is in the future', 400);
    }
  }

  const now = Date.now();
  const updated = await db
    .update(tasks)
    .set({ ...data, updatedAt: now })
    .where(and(eq(tasks.id, req.params.id), eq(tasks.userId, userId)))
    .returning();

  const taskSubtasks = await db
    .select()
    .from(subtasks)
    .where(and(eq(subtasks.taskId, req.params.id), eq(subtasks.userId, userId)))
    .orderBy(asc(subtasks.order));

  res.json({ ...updated[0], subtasks: taskSubtasks });
}));

router.delete('/:id', asyncHandler(async (req: Request, res: Response) => {
  const userId = req.user!.userId;

  const existing = await db
    .select()
    .from(tasks)
    .where(and(eq(tasks.id, req.params.id), eq(tasks.userId, userId)))
    .limit(1);

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

  await db
    .delete(tasks)
    .where(and(eq(tasks.id, req.params.id), eq(tasks.userId, userId)));

  res.status(204).send();
}));

router.post('/batch', asyncHandler(async (req: Request, res: Response) => {
  const { operations } = z.object({
    operations: z.array(
      z.union([
        z.object({ type: z.literal('create'), data: taskCreateSchema }),
        z.object({ type: z.literal('update'), id: z.string(), data: taskUpdateSchema }),
        z.object({ type: z.literal('delete'), id: z.string() }),
      ])
    ),
  }).parse(req.body);

  const userId = req.user!.userId;
  const results: any[] = [];

  for (const op of operations) {
    try {
      if (op.type === 'create') {
        if (op.data.categoryId) {
          const cat = await db
            .select()
            .from(categories)
            .where(and(eq(categories.id, op.data.categoryId), eq(categories.userId, userId)))
            .limit(1);
          if (cat.length === 0) throw new AppError('NOT_FOUND', 'Category not found', 404);
        }

        const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
        const now = Date.now();
        
        await db.insert(tasks).values({
          id: taskId,
          userId,
          ...op.data,
          completed: false,
          createdAt: now,
          updatedAt: now,
        });

        results.push({ id: taskId, success: true });
      } else if (op.type === 'update') {
        if (op.data.completed === true) {
          let effectiveDueDate: number;
          if (op.data.dueDate !== undefined) {
            effectiveDueDate = op.data.dueDate;
          } else {
            const existingTask = await db
              .select({ dueDate: tasks.dueDate })
              .from(tasks)
              .where(and(eq(tasks.id, op.id), eq(tasks.userId, userId)))
              .limit(1);
            if (existingTask.length === 0) throw new AppError('NOT_FOUND', 'Task not found', 404);
            effectiveDueDate = existingTask[0].dueDate;
          }
          if (!canCompleteTask(effectiveDueDate)) {
            throw new AppError('VALIDATION_ERROR', 'Cannot mark a task as completed if its due date is in the future', 400);
          }
        }

        await db
          .update(tasks)
          .set({ ...op.data, updatedAt: Date.now() })
          .where(and(eq(tasks.id, op.id), eq(tasks.userId, userId)));
        results.push({ id: op.id, success: true });
      } else if (op.type === 'delete') {
        await db
          .delete(tasks)
          .where(and(eq(tasks.id, op.id), eq(tasks.userId, userId)));
        results.push({ id: op.id, success: true });
      }
    } catch (error) {
      results.push({ 
        id: 'id' in op ? op.id : 'unknown', 
        success: false, 
        error: error instanceof Error ? error.message : 'Unknown error' 
      });
    }
  }

  res.json({ results });
}));

export default router;