import { eq, and, desc, asc, sql } from 'drizzle-orm';
import { db } from '../db/index';
import { tasks, pipelines } from '../db/schema';
import { eventBus } from '../events/bus';

// ═══════════════════════════════════════════════════════════
// Task Service — Decision queue management
// ═══════════════════════════════════════════════════════════

export async function listTasks(filters: {
  status?: string;
  priority?: string;
  type?: string;
  pipelineId?: string;
  assigneeId?: string;
  slaBreached?: boolean;
  sort?: string;
  page?: number;
  limit?: number;
}) {
  const { page = 1, limit = 20 } = filters;
  const offset = (page - 1) * limit;

  const conditions = [];
  if (filters.status) conditions.push(eq(tasks.status, filters.status));
  if (filters.priority) conditions.push(eq(tasks.priority, filters.priority));
  if (filters.type) conditions.push(eq(tasks.type, filters.type));
  if (filters.pipelineId) conditions.push(eq(tasks.pipelineId, filters.pipelineId));
  if (filters.assigneeId) conditions.push(eq(tasks.assigneeId, filters.assigneeId));
  if (filters.slaBreached !== undefined) conditions.push(eq(tasks.slaBreached, filters.slaBreached));

  const where = conditions.length > 0 ? and(...conditions) : undefined;

  // Sort: default is priority desc + sla deadline asc (most urgent first)
  const orderBy = filters.sort === '-created_at'
    ? [desc(tasks.createdAt)]
    : filters.sort === 'created_at'
    ? [asc(tasks.createdAt)]
    : [
        asc(sql`CASE ${tasks.priority}
          WHEN 'critical' THEN 0
          WHEN 'high' THEN 1
          WHEN 'medium' THEN 2
          WHEN 'low' THEN 3
          ELSE 4
        END`),
        asc(tasks.slaDeadline),
      ];

  const [items, countResult] = await Promise.all([
    db
      .select()
      .from(tasks)
      .where(where)
      .orderBy(...orderBy)
      .limit(limit)
      .offset(offset),
    db
      .select({ count: sql<number>`count(*)` })
      .from(tasks)
      .where(where),
  ]);

  const total = Number(countResult[0]?.count || 0);

  return {
    items,
    meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
  };
}

export async function getTask(id: string) {
  return db.query.tasks.findFirst({
    where: eq(tasks.id, id),
    with: {
      pipeline: true,
      approvals: true,
    },
  });
}

export async function claimTask(taskId: string, userId: string) {
  const task = await db.query.tasks.findFirst({
    where: eq(tasks.id, taskId),
  });

  if (!task) throw new Error('Task not found');
  if (task.status !== 'pending') throw new Error('Task is not pending');

  const now = new Date();

  const [updated] = await db
    .update(tasks)
    .set({
      status: 'claimed',
      claimedAt: now,
      claimedBy: userId,
      updatedAt: now,
    })
    .where(and(eq(tasks.id, taskId), eq(tasks.status, 'pending')))
    .returning();

  if (!updated) throw new Error('Task was claimed by someone else');

  await eventBus.emit({
    type: 'task.claimed',
    timestamp: now.toISOString(),
    data: { taskId, claimedBy: userId },
    actor: { type: 'user', id: userId },
  });

  return updated;
}

export async function completeTask(
  taskId: string,
  decision: string,
  userId: string,
  opts?: { notes?: string; data?: Record<string, unknown> }
) {
  const task = await db.query.tasks.findFirst({
    where: eq(tasks.id, taskId),
  });

  if (!task) throw new Error('Task not found');
  if (task.status === 'completed') throw new Error('Task is already completed');

  const now = new Date();

  const [updated] = await db
    .update(tasks)
    .set({
      status: 'completed',
      decision,
      decisionNotes: opts?.notes || null,
      decisionData: opts?.data || {},
      decidedAt: now,
      decidedBy: userId,
      updatedAt: now,
    })
    .where(eq(tasks.id, taskId))
    .returning();

  await eventBus.emit({
    type: 'task.completed',
    timestamp: now.toISOString(),
    data: {
      taskId,
      pipelineId: task.pipelineId,
      decision,
      decidedBy: userId,
      notes: opts?.notes || null,
    },
    actor: { type: 'user', id: userId },
  });

  return updated;
}

export async function reassignTask(
  taskId: string,
  assigneeId: string,
  userId: string,
  reason?: string
) {
  const task = await db.query.tasks.findFirst({
    where: eq(tasks.id, taskId),
  });

  if (!task) throw new Error('Task not found');
  if (task.status === 'completed') throw new Error('Cannot reassign a completed task');

  const now = new Date();
  const previousAssignee = task.assigneeId;

  const [updated] = await db
    .update(tasks)
    .set({
      assigneeId,
      updatedAt: now,
    })
    .where(eq(tasks.id, taskId))
    .returning();

  await eventBus.emit({
    type: 'task.claimed', // reuse — reassignment is a form of claim
    timestamp: now.toISOString(),
    data: {
      taskId,
      pipelineId: task.pipelineId,
      previousAssignee,
      newAssignee: assigneeId,
      reason: reason || null,
      reassignedBy: userId,
    },
    actor: { type: 'user', id: userId },
  });

  return updated;
}

export async function getTaskStats() {
  // Status counts
  const statusResults = await db
    .select({
      status: tasks.status,
      priority: tasks.priority,
      type: tasks.type,
      count: sql<number>`count(*)`,
    })
    .from(tasks)
    .where(
      sql`${tasks.status} IN ('pending', 'claimed', 'in_progress')`
    )
    .groupBy(tasks.status, tasks.priority, tasks.type);

  const stats = {
    pending: 0,
    inProgress: 0,
    blocked: 0,
    avgWaitTimeMinutes: 0,
    slaBreaches: 0,
    byPriority: { critical: 0, high: 0, medium: 0, low: 0 } as Record<string, number>,
    byType: { approval: 0, review: 0, decision: 0, manual_action: 0, fix_required: 0 } as Record<string, number>,
  };

  for (const row of statusResults) {
    const count = Number(row.count);
    if (row.status === 'pending') stats.pending += count;
    if (row.status === 'claimed' || row.status === 'in_progress') stats.inProgress += count;

    if (row.priority in stats.byPriority) {
      stats.byPriority[row.priority] += count;
    }
    if (row.type in stats.byType) {
      stats.byType[row.type] += count;
    }
  }

  // Average wait time for completed tasks (last 100)
  const [avgWait] = await db
    .select({
      avgWait: sql<number>`
        COALESCE(
          AVG(EXTRACT(EPOCH FROM (${tasks.decidedAt} - ${tasks.createdAt})) / 60),
          0
        )
      `,
    })
    .from(tasks)
    .where(eq(tasks.status, 'completed'))
    .limit(100);

  stats.avgWaitTimeMinutes = Math.round(Number(avgWait?.avgWait || 0));

  // SLA breaches
  const [breaches] = await db
    .select({ count: sql<number>`count(*)` })
    .from(tasks)
    .where(
      and(
        eq(tasks.slaBreached, true),
        sql`${tasks.status} IN ('pending', 'claimed')`
      )
    );

  stats.slaBreaches = Number(breaches?.count || 0);

  // Blocked = tasks that block stage advance and are pending
  const [blocked] = await db
    .select({ count: sql<number>`count(*)` })
    .from(tasks)
    .where(
      and(
        eq(tasks.blocksStageAdvance, true),
        sql`${tasks.status} IN ('pending', 'claimed')`
      )
    );

  stats.blocked = Number(blocked?.count || 0);

  return stats;
}

export async function getBlockers(pipelineId?: string) {
  const conditions = [
    eq(tasks.blocksStageAdvance, true),
    sql`${tasks.status} IN ('pending', 'claimed')`,
  ];
  if (pipelineId) {
    conditions.push(eq(tasks.pipelineId, pipelineId));
  }

  const blockerTasks = await db
    .select()
    .from(tasks)
    .where(and(...conditions))
    .orderBy(
      asc(sql`CASE ${tasks.priority}
        WHEN 'critical' THEN 0
        WHEN 'high' THEN 1
        WHEN 'medium' THEN 2
        WHEN 'low' THEN 3
        ELSE 4
      END`),
      asc(tasks.slaDeadline),
    );

  // Enrich with pipeline names
  const pipelineIds = [...new Set(blockerTasks.map((t) => t.pipelineId).filter(Boolean))];
  const pipelineMap: Record<string, string> = {};

  if (pipelineIds.length > 0) {
    const pipelineRecords = await db
      .select({ id: pipelines.id, name: pipelines.name })
      .from(pipelines)
      .where(sql`${pipelines.id} IN ${pipelineIds}`);

    for (const p of pipelineRecords) {
      pipelineMap[p.id] = p.name;
    }
  }

  return blockerTasks.map((t) => ({
    id: t.id,
    title: t.title,
    pipelineId: t.pipelineId,
    pipelineName: t.pipelineId ? pipelineMap[t.pipelineId] || 'Unknown' : 'N/A',
    blockerType: t.type || 'task',
    blockReason: t.description || 'Blocks stage advance',
    priority: t.priority,
    hoursBlocked: t.createdAt
      ? (Date.now() - new Date(t.createdAt).getTime()) / (1000 * 60 * 60)
      : 0,
    suggestedAction:
      t.type === 'approval'
        ? 'Use factory_approve_task or factory_reject_task'
        : t.type === 'review'
          ? 'Complete the review'
          : 'Address the blocking task',
  }));
}

export async function bulkAction(
  action: string,
  taskIds: string[],
  userId: string,
  opts?: { notes?: string; assigneeId?: string }
) {
  const succeeded: string[] = [];
  const failed: Array<{ taskId: string; error: string }> = [];

  for (const taskId of taskIds) {
    try {
      switch (action) {
        case 'approve':
          await completeTask(taskId, 'approved', userId, { notes: opts?.notes });
          succeeded.push(taskId);
          break;
        case 'reject':
          await completeTask(taskId, 'rejected', userId, { notes: opts?.notes });
          succeeded.push(taskId);
          break;
        case 'defer':
          await completeTask(taskId, 'deferred', userId, { notes: opts?.notes });
          succeeded.push(taskId);
          break;
        default:
          failed.push({ taskId, error: `Unknown action: ${action}` });
      }
    } catch (err: any) {
      failed.push({ taskId, error: err.message });
    }
  }

  return { succeeded, failed };
}
