import type { Prisma, TaskStatus, StoryStatus, PbiStatus, SprintStatus } from '@prisma/client'
import { prisma } from '@/lib/prisma'

export interface PropagationResult {
  task: {
    id: string
    title: string
    status: TaskStatus
    story_id: string
    implementation_plan: string | null
  }
  storyId: string
  storyChanged: boolean
  pbiChanged: boolean
  sprintChanged: boolean
  sprintRunChanged: boolean
}

// Real-time status-propagatie: bij elke task-statuswijziging wordt de keten
// Task → Story → PBI → Sprint → SprintRun herevalueerd binnen één transactie.
//
// Regels:
//   Story:  ANY task FAILED → FAILED, ELSE ALL DONE → DONE,
//           ELSE IN_SPRINT (mits story.sprint_id != null), anders OPEN
//   PBI:    ANY story FAILED → FAILED, ELSE ALL DONE → DONE, ELSE READY
//           (BLOCKED is handmatig en wordt niet overschreven door deze helper)
//   Sprint: ANY PBI van een story-in-sprint FAILED → FAILED,
//           ELSE ALL PBIs van die stories DONE → COMPLETED,
//           ELSE ACTIVE
//   SprintRun: Sprint→FAILED → SprintRun=FAILED + cancel openstaand werk +
//              zet failed_task_id; Sprint→COMPLETED → SprintRun=DONE; anders
//              blijft SprintRun ongewijzigd.
export async function propagateStatusUpwards(
  taskId: string,
  newStatus: TaskStatus,
  client?: Prisma.TransactionClient,
): Promise<PropagationResult> {
  const run = async (tx: Prisma.TransactionClient): Promise<PropagationResult> => {
    const task = await tx.task.update({
      where: { id: taskId },
      data: { status: newStatus },
      select: {
        id: true,
        title: true,
        status: true,
        story_id: true,
        implementation_plan: true,
      },
    })

    // Story herevalueren
    const siblings = await tx.task.findMany({
      where: { story_id: task.story_id },
      select: { status: true },
    })
    const anyTaskFailed = siblings.some((s) => s.status === 'FAILED')
    const allTasksDone =
      siblings.length > 0 && siblings.every((s) => s.status === 'DONE')

    const story = await tx.story.findUniqueOrThrow({
      where: { id: task.story_id },
      select: { id: true, status: true, pbi_id: true, sprint_id: true },
    })

    const defaultActive: StoryStatus = story.sprint_id ? 'IN_SPRINT' : 'OPEN'
    let nextStoryStatus: StoryStatus
    if (anyTaskFailed) nextStoryStatus = 'FAILED'
    else if (allTasksDone) nextStoryStatus = 'DONE'
    else nextStoryStatus = defaultActive

    let storyChanged = false
    if (nextStoryStatus !== story.status) {
      await tx.story.update({
        where: { id: story.id },
        data: { status: nextStoryStatus },
      })
      storyChanged = true
    }

    // PBI herevalueren — BLOCKED met rust laten
    const pbi = await tx.pbi.findUniqueOrThrow({
      where: { id: story.pbi_id },
      select: { id: true, status: true },
    })

    let pbiChanged = false
    if (pbi.status !== 'BLOCKED') {
      const pbiStories = await tx.story.findMany({
        where: { pbi_id: pbi.id },
        select: { status: true },
      })
      const anyStoryFailed = pbiStories.some((s) => s.status === 'FAILED')
      const allStoriesDone =
        pbiStories.length > 0 && pbiStories.every((s) => s.status === 'DONE')

      let nextPbiStatus: PbiStatus
      if (anyStoryFailed) nextPbiStatus = 'FAILED'
      else if (allStoriesDone) nextPbiStatus = 'DONE'
      else nextPbiStatus = 'READY'

      if (nextPbiStatus !== pbi.status) {
        await tx.pbi.update({
          where: { id: pbi.id },
          data: { status: nextPbiStatus },
        })
        pbiChanged = true
      }
    }

    // Sprint herevalueren — alleen als deze story aan een sprint hangt
    let sprintChanged = false
    let nextSprintStatus: SprintStatus | null = null
    if (story.sprint_id) {
      const sprint = await tx.sprint.findUniqueOrThrow({
        where: { id: story.sprint_id },
        select: { id: true, status: true },
      })

      const sprintPbiRows = await tx.story.findMany({
        where: { sprint_id: sprint.id },
        select: { pbi_id: true },
        distinct: ['pbi_id'],
      })
      const sprintPbis = await tx.pbi.findMany({
        where: { id: { in: sprintPbiRows.map((s) => s.pbi_id) } },
        select: { status: true },
      })
      const anyPbiFailed = sprintPbis.some((p) => p.status === 'FAILED')
      const allPbisDone =
        sprintPbis.length > 0 && sprintPbis.every((p) => p.status === 'DONE')

      let nextStatus: SprintStatus
      if (anyPbiFailed) nextStatus = 'FAILED'
      else if (allPbisDone) nextStatus = 'CLOSED'
      else nextStatus = 'OPEN'

      if (nextStatus !== sprint.status) {
        await tx.sprint.update({
          where: { id: sprint.id },
          data: {
            status: nextStatus,
            ...(nextStatus === 'CLOSED' ? { completed_at: new Date() } : {}),
          },
        })
        sprintChanged = true
        nextSprintStatus = nextStatus
      }
    }

    // SprintRun herevalueren — via ClaudeJob.sprint_run_id van deze task
    let sprintRunChanged = false
    if (nextSprintStatus === 'FAILED' || nextSprintStatus === 'CLOSED') {
      const job = await tx.claudeJob.findFirst({
        where: { task_id: taskId, sprint_run_id: { not: null } },
        orderBy: { created_at: 'desc' },
        select: { id: true, sprint_run_id: true },
      })

      if (job?.sprint_run_id) {
        const sprintRun = await tx.sprintRun.findUnique({
          where: { id: job.sprint_run_id },
          select: { id: true, status: true },
        })
        if (
          sprintRun &&
          (sprintRun.status === 'QUEUED' ||
            sprintRun.status === 'RUNNING' ||
            sprintRun.status === 'PAUSED')
        ) {
          if (nextSprintStatus === 'FAILED') {
            await tx.sprintRun.update({
              where: { id: sprintRun.id },
              data: {
                status: 'FAILED',
                finished_at: new Date(),
                failed_task_id: taskId,
              },
            })
            await tx.claudeJob.updateMany({
              where: {
                sprint_run_id: sprintRun.id,
                status: { in: ['QUEUED', 'CLAIMED', 'RUNNING'] },
                id: { not: job.id },
              },
              data: {
                status: 'CANCELLED',
                finished_at: new Date(),
                error: `Cancelled: task ${taskId} failed in same sprint run`,
              },
            })
            sprintRunChanged = true
          } else {
            // COMPLETED
            await tx.sprintRun.update({
              where: { id: sprintRun.id },
              data: { status: 'DONE', finished_at: new Date() },
            })
            sprintRunChanged = true
          }
        }
      }
    }

    return {
      task,
      storyId: task.story_id,
      storyChanged,
      pbiChanged,
      sprintChanged,
      sprintRunChanged,
    }
  }

  if (client) return run(client)
  return prisma.$transaction(run)
}
