import { eq, and, desc, sql, asc, ilike } from 'drizzle-orm';
import { db } from '../db/index';
import { pipelines, pipelineStages, tasks, approvals } from '../db/schema';
import { eventBus } from '../events/bus';
import { PIPELINE_STAGES_ORDERED, type PipelineStage } from '../types/index';
import { v4 as uuidv4 } from 'uuid';

// ═══════════════════════════════════════════════════════════
// Pipeline Service — Core business logic
// ═══════════════════════════════════════════════════════════

function slugify(name: string): string {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');
}

// Stage definitions with gate config
const STAGE_DEFINITIONS: Record<PipelineStage, { requiresApproval: boolean; autoAdvance: boolean; order: number }> = {
  intake: { requiresApproval: false, autoAdvance: true, order: 0 },
  scaffolding: { requiresApproval: false, autoAdvance: true, order: 1 },
  building: { requiresApproval: false, autoAdvance: true, order: 2 },
  testing: { requiresApproval: false, autoAdvance: true, order: 3 },
  review: { requiresApproval: true, autoAdvance: false, order: 4 },
  staging: { requiresApproval: true, autoAdvance: false, order: 5 },
  production: { requiresApproval: true, autoAdvance: false, order: 6 },
  published: { requiresApproval: false, autoAdvance: false, order: 7 },
};

export async function createPipeline(data: {
  name: string;
  template?: string;
  platform?: string;
  priority?: string;
  config?: Record<string, unknown>;
  assigneeId?: string;
  createdBy?: string;
}) {
  const slug = slugify(data.name) + '-' + Date.now().toString(36);

  // Create the pipeline
  const [pipeline] = await db
    .insert(pipelines)
    .values({
      name: data.name,
      slug,
      template: data.template || 'mcp-server-standard',
      platform: data.platform || '',
      priority: data.priority || 'medium',
      config: data.config || {},
      assigneeId: data.assigneeId,
      createdBy: data.createdBy,
      currentStage: 'intake',
      status: 'active',
    })
    .returning();

  // Create all stage records
  const stageValues = PIPELINE_STAGES_ORDERED.map((stageName, idx) => ({
    pipelineId: pipeline.id,
    stageName,
    stageOrder: idx,
    status: stageName === 'intake' ? 'active' : 'pending',
    requiresApproval: STAGE_DEFINITIONS[stageName].requiresApproval,
    autoAdvance: STAGE_DEFINITIONS[stageName].autoAdvance,
    enteredAt: stageName === 'intake' ? new Date() : null,
  }));

  await db.insert(pipelineStages).values(stageValues);

  // Emit event
  await eventBus.emit({
    type: 'pipeline.created',
    timestamp: new Date().toISOString(),
    data: {
      pipelineId: pipeline.id,
      name: pipeline.name,
      template: pipeline.template,
      priority: pipeline.priority,
    },
    actor: data.createdBy
      ? { type: 'user', id: data.createdBy }
      : { type: 'system', id: 'system' },
  });

  return pipeline;
}

export async function listPipelines(filters: {
  status?: string;
  stage?: string;
  priority?: string;
  assigneeId?: string;
  search?: string;
  page?: number;
  limit?: number;
}) {
  const { page = 1, limit = 20 } = filters;
  const offset = (page - 1) * limit;

  const conditions = [];
  if (filters.status) conditions.push(eq(pipelines.status, filters.status));
  if (filters.stage) conditions.push(eq(pipelines.currentStage, filters.stage));
  if (filters.priority) conditions.push(eq(pipelines.priority, filters.priority));
  if (filters.assigneeId) conditions.push(eq(pipelines.assigneeId, filters.assigneeId));
  if (filters.search) conditions.push(ilike(pipelines.name, `%${filters.search}%`));

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

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

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

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

export async function getPipeline(id: string) {
  const pipeline = await db.query.pipelines.findFirst({
    where: eq(pipelines.id, id),
    with: {
      stages: {
        orderBy: [asc(pipelineStages.stageOrder)],
      },
      tasks: {
        orderBy: [desc(tasks.createdAt)],
        limit: 20,
      },
      approvals: {
        orderBy: [desc(approvals.createdAt)],
        limit: 10,
      },
    },
  });

  return pipeline || null;
}

export async function updatePipeline(
  id: string,
  data: {
    priority?: string;
    status?: string;
    config?: Record<string, unknown>;
    assigneeId?: string | null;
    metadata?: Record<string, unknown>;
  },
  userId?: string
) {
  const existing = await db.query.pipelines.findFirst({
    where: eq(pipelines.id, id),
  });

  if (!existing) throw new Error('Pipeline not found');
  if (existing.status === 'archived') throw new Error('Pipeline is archived');

  const now = new Date();
  const updates: Record<string, unknown> = { updatedAt: now };

  const changes: Record<string, { from: unknown; to: unknown }> = {};

  if (data.priority !== undefined && data.priority !== existing.priority) {
    updates.priority = data.priority;
    changes.priority = { from: existing.priority, to: data.priority };
  }
  if (data.status !== undefined && data.status !== existing.status) {
    updates.status = data.status;
    changes.status = { from: existing.status, to: data.status };
    if (data.status === 'completed') updates.completedAt = now;
  }
  if (data.config !== undefined) {
    updates.config = data.config;
    changes.config = { from: existing.config, to: data.config };
  }
  if (data.assigneeId !== undefined) {
    updates.assigneeId = data.assigneeId;
    changes.assigneeId = { from: existing.assigneeId, to: data.assigneeId };
  }
  if (data.metadata !== undefined) {
    updates.metadata = data.metadata;
    changes.metadata = { from: existing.metadata, to: data.metadata };
  }

  const [updated] = await db
    .update(pipelines)
    .set(updates)
    .where(eq(pipelines.id, id))
    .returning();

  // Emit event
  await eventBus.emit({
    type: 'pipeline.updated',
    timestamp: now.toISOString(),
    data: {
      pipelineId: id,
      pipelineName: existing.name,
      changes,
    },
    actor: userId
      ? { type: 'user', id: userId }
      : { type: 'system', id: 'system' },
  });

  return updated;
}

export async function archivePipeline(id: string, userId?: string) {
  const existing = await db.query.pipelines.findFirst({
    where: eq(pipelines.id, id),
  });

  if (!existing) throw new Error('Pipeline not found');
  if (existing.status === 'archived') throw new Error('Pipeline is already archived');

  const now = new Date();

  const [updated] = await db
    .update(pipelines)
    .set({
      status: 'archived',
      updatedAt: now,
    })
    .where(eq(pipelines.id, id))
    .returning();

  await eventBus.emit({
    type: 'pipeline.updated',
    timestamp: now.toISOString(),
    data: {
      pipelineId: id,
      pipelineName: existing.name,
      changes: { status: { from: existing.status, to: 'archived' } },
    },
    actor: userId
      ? { type: 'user', id: userId }
      : { type: 'system', id: 'system' },
  });

  return updated;
}

export async function getStages(pipelineId: string) {
  const pipeline = await db.query.pipelines.findFirst({
    where: eq(pipelines.id, pipelineId),
  });

  if (!pipeline) return null;

  return db
    .select()
    .from(pipelineStages)
    .where(eq(pipelineStages.pipelineId, pipelineId))
    .orderBy(asc(pipelineStages.stageOrder));
}

export async function advanceStage(
  pipelineId: string,
  opts: {
    targetStage?: PipelineStage;
    skipValidation?: boolean;
    notes?: string;
    triggeredBy?: string;
  }
) {
  const pipeline = await db.query.pipelines.findFirst({
    where: eq(pipelines.id, pipelineId),
    with: {
      stages: { orderBy: [asc(pipelineStages.stageOrder)] },
    },
  });

  if (!pipeline) throw new Error('Pipeline not found');
  if (pipeline.status !== 'active') throw new Error('Pipeline is not active');

  const currentIdx = PIPELINE_STAGES_ORDERED.indexOf(pipeline.currentStage as PipelineStage);
  const targetStage = opts.targetStage || PIPELINE_STAGES_ORDERED[currentIdx + 1];

  if (!targetStage) throw new Error('Pipeline is already at the final stage');

  const targetIdx = PIPELINE_STAGES_ORDERED.indexOf(targetStage);
  if (targetIdx <= currentIdx) throw new Error(`Cannot go backwards from ${pipeline.currentStage} to ${targetStage}`);

  const targetDef = STAGE_DEFINITIONS[targetStage];

  // Check if target stage requires approval and we're not skipping
  if (targetDef.requiresApproval && !opts.skipValidation) {
    const currentDef = STAGE_DEFINITIONS[pipeline.currentStage as PipelineStage];
    if (currentDef.requiresApproval) {
      const approvalRecord = await db.query.approvals.findFirst({
        where: and(
          eq(approvals.pipelineId, pipelineId),
          eq(approvals.stageName, pipeline.currentStage),
          eq(approvals.status, 'approved')
        ),
      });

      if (!approvalRecord) {
        throw new Error(`Stage '${pipeline.currentStage}' requires approval before advancing`);
      }
    }
  }

  const now = new Date();

  // Complete the current stage
  await db
    .update(pipelineStages)
    .set({
      status: 'completed',
      completedAt: now,
      durationSeconds: pipeline.stages.find((s) => s.stageName === pipeline.currentStage)?.enteredAt
        ? Math.floor((now.getTime() - new Date(pipeline.stages.find((s) => s.stageName === pipeline.currentStage)!.enteredAt!).getTime()) / 1000)
        : null,
    })
    .where(
      and(
        eq(pipelineStages.pipelineId, pipelineId),
        eq(pipelineStages.stageName, pipeline.currentStage)
      )
    );

  // Activate the target stage
  await db
    .update(pipelineStages)
    .set({
      status: 'active',
      enteredAt: now,
    })
    .where(
      and(
        eq(pipelineStages.pipelineId, pipelineId),
        eq(pipelineStages.stageName, targetStage)
      )
    );

  // Update the pipeline
  const isCompleted = targetStage === 'published';
  await db
    .update(pipelines)
    .set({
      currentStage: targetStage,
      status: isCompleted ? 'completed' : 'active',
      completedAt: isCompleted ? now : null,
      updatedAt: now,
    })
    .where(eq(pipelines.id, pipelineId));

  // If the new stage requires approval, create an approval task
  if (targetDef.requiresApproval) {
    const [task] = await db
      .insert(tasks)
      .values({
        pipelineId,
        stageName: targetStage,
        type: 'approval',
        title: `Approve ${pipeline.name} — ${targetStage}`,
        description: `Pipeline "${pipeline.name}" has reached the ${targetStage} stage and requires approval to proceed.`,
        context: {
          pipelineName: pipeline.name,
          fromStage: pipeline.currentStage,
          toStage: targetStage,
          notes: opts.notes,
        },
        status: 'pending',
        priority: pipeline.priority,
        assigneeId: pipeline.assigneeId,
        blocksStageAdvance: true,
        blocksPipelineId: pipelineId,
        slaDeadline: new Date(Date.now() + 4 * 60 * 60 * 1000), // 4h SLA
      })
      .returning();

    // Create approval record
    await db.insert(approvals).values({
      taskId: task.id,
      pipelineId,
      stageName: targetStage,
      type: 'stage_gate',
      status: 'pending',
    });

    // Emit task created event
    await eventBus.emit({
      type: 'task.created',
      timestamp: now.toISOString(),
      data: {
        taskId: task.id,
        pipelineId,
        type: 'approval',
        title: task.title,
        priority: pipeline.priority,
        slaDeadline: task.slaDeadline?.toISOString() || null,
        blocksStageAdvance: true,
      },
    });
  }

  // Emit stage change event
  await eventBus.emit({
    type: 'pipeline.stage_changed',
    timestamp: now.toISOString(),
    data: {
      pipelineId,
      pipelineName: pipeline.name,
      fromStage: pipeline.currentStage,
      toStage: targetStage,
      triggeredBy: opts.triggeredBy || 'system',
    },
    actor: opts.triggeredBy
      ? { type: 'user', id: opts.triggeredBy }
      : { type: 'system', id: 'system' },
  });

  // Fetch the updated stage record
  const updatedStage = await db.query.pipelineStages.findFirst({
    where: and(
      eq(pipelineStages.pipelineId, pipelineId),
      eq(pipelineStages.stageName, targetStage)
    ),
  });

  // Fetch any tasks created for this stage
  const tasksCreated = await db
    .select()
    .from(tasks)
    .where(
      and(
        eq(tasks.pipelineId, pipelineId),
        eq(tasks.stageName, targetStage)
      )
    )
    .orderBy(desc(tasks.createdAt))
    .limit(10);

  return {
    stage: updatedStage || { stageName: targetStage, status: 'active' },
    tasksCreated,
  };
}
