import { beforeEach, describe, expect, it } from 'vitest'
import { reconcileActiveRunsFromTasks } from '@/lib/run-runtime/reconcile'
import { RUN_STATUS, RUN_STEP_STATUS } from '@/lib/run-runtime/types'
import { TASK_STATUS, TASK_TYPE } from '@/lib/task/types'
import { prisma } from '../../helpers/prisma'
import { resetBillingState } from '../../helpers/db-reset'
import { createTestUser } from '../../helpers/billing-fixtures'

describe('run runtime reconcileActiveRunsFromTasks', () => {
  beforeEach(async () => {
    await resetBillingState()
  })

  it('marks a running run completed when the linked task already completed', async () => {
    const user = await createTestUser()
    const finishedAt = new Date('2026-03-30T08:00:00.000Z')
    const task = await prisma.task.create({
      data: {
        userId: user.id,
        projectId: 'project-run-complete',
        episodeId: 'episode-run-complete',
        type: TASK_TYPE.STORY_TO_SCRIPT_RUN,
        targetType: 'NovelPromotionEpisode',
        targetId: 'episode-run-complete',
        status: TASK_STATUS.COMPLETED,
        progress: 100,
        payload: { episodeId: 'episode-run-complete' },
        result: {
          episodeId: 'episode-run-complete',
          persistedClips: 12,
        },
        queuedAt: new Date('2026-03-30T07:55:00.000Z'),
        startedAt: new Date('2026-03-30T07:56:00.000Z'),
        finishedAt,
      },
    })
    const run = await prisma.graphRun.create({
      data: {
        userId: user.id,
        projectId: 'project-run-complete',
        episodeId: 'episode-run-complete',
        workflowType: TASK_TYPE.STORY_TO_SCRIPT_RUN,
        taskType: TASK_TYPE.STORY_TO_SCRIPT_RUN,
        taskId: task.id,
        targetType: 'NovelPromotionEpisode',
        targetId: 'episode-run-complete',
        status: RUN_STATUS.RUNNING,
        leaseOwner: 'worker:story-to-script',
        leaseExpiresAt: new Date('2026-03-30T08:05:00.000Z'),
        heartbeatAt: new Date('2026-03-30T07:59:30.000Z'),
        queuedAt: new Date('2026-03-30T07:55:00.000Z'),
        startedAt: new Date('2026-03-30T07:56:00.000Z'),
      },
    })
    await prisma.graphStep.create({
      data: {
        runId: run.id,
        stepKey: 'story_to_script_persist',
        stepTitle: 'Persist screenplay',
        status: RUN_STEP_STATUS.RUNNING,
        currentAttempt: 1,
        stepIndex: 4,
        stepTotal: 4,
        startedAt: new Date('2026-03-30T07:58:00.000Z'),
      },
    })

    const reconciled = await reconcileActiveRunsFromTasks()

    expect(reconciled).toEqual([{
      runId: run.id,
      taskId: task.id,
      nextStatus: 'completed',
      reason: 'linked task already completed',
    }])

    const refreshedRun = await prisma.graphRun.findUnique({ where: { id: run.id } })
    expect(refreshedRun).toMatchObject({
      status: RUN_STATUS.COMPLETED,
      output: {
        episodeId: 'episode-run-complete',
        persistedClips: 12,
      },
      errorCode: null,
      errorMessage: null,
      leaseOwner: null,
      leaseExpiresAt: null,
      heartbeatAt: null,
    })
    expect(refreshedRun?.finishedAt?.toISOString()).toBe(finishedAt.toISOString())

    const refreshedStep = await prisma.graphStep.findUnique({
      where: {
        runId_stepKey: {
          runId: run.id,
          stepKey: 'story_to_script_persist',
        },
      },
    })
    expect(refreshedStep).toMatchObject({
      status: RUN_STEP_STATUS.COMPLETED,
      lastErrorCode: null,
      lastErrorMessage: null,
    })
    expect(refreshedStep?.finishedAt?.toISOString()).toBe(finishedAt.toISOString())
  })

  it('marks a running run failed when the linked task already failed', async () => {
    const user = await createTestUser()
    const finishedAt = new Date('2026-03-30T09:00:00.000Z')
    const task = await prisma.task.create({
      data: {
        userId: user.id,
        projectId: 'project-run-failed',
        episodeId: 'episode-run-failed',
        type: TASK_TYPE.STORY_TO_SCRIPT_RUN,
        targetType: 'NovelPromotionEpisode',
        targetId: 'episode-run-failed',
        status: TASK_STATUS.FAILED,
        progress: 72,
        payload: { episodeId: 'episode-run-failed' },
        errorCode: 'WATCHDOG_TIMEOUT',
        errorMessage: 'Task heartbeat timeout',
        queuedAt: new Date('2026-03-30T08:50:00.000Z'),
        startedAt: new Date('2026-03-30T08:51:00.000Z'),
        finishedAt,
      },
    })
    const run = await prisma.graphRun.create({
      data: {
        userId: user.id,
        projectId: 'project-run-failed',
        episodeId: 'episode-run-failed',
        workflowType: TASK_TYPE.STORY_TO_SCRIPT_RUN,
        taskType: TASK_TYPE.STORY_TO_SCRIPT_RUN,
        taskId: task.id,
        targetType: 'NovelPromotionEpisode',
        targetId: 'episode-run-failed',
        status: RUN_STATUS.RUNNING,
        leaseOwner: 'worker:story-to-script',
        leaseExpiresAt: new Date('2026-03-30T08:55:00.000Z'),
        heartbeatAt: new Date('2026-03-30T08:54:00.000Z'),
        queuedAt: new Date('2026-03-30T08:50:00.000Z'),
        startedAt: new Date('2026-03-30T08:51:00.000Z'),
      },
    })
    await prisma.graphStep.create({
      data: {
        runId: run.id,
        stepKey: 'screenplay_clip-1',
        stepTitle: 'Screenplay clip 1',
        status: RUN_STEP_STATUS.RUNNING,
        currentAttempt: 1,
        stepIndex: 3,
        stepTotal: 6,
        startedAt: new Date('2026-03-30T08:52:00.000Z'),
      },
    })

    const reconciled = await reconcileActiveRunsFromTasks()

    expect(reconciled).toEqual([{
      runId: run.id,
      taskId: task.id,
      nextStatus: 'failed',
      reason: 'linked task already failed',
    }])

    const refreshedRun = await prisma.graphRun.findUnique({ where: { id: run.id } })
    expect(refreshedRun).toMatchObject({
      status: RUN_STATUS.FAILED,
      errorCode: 'WATCHDOG_TIMEOUT',
      errorMessage: 'Task heartbeat timeout',
      leaseOwner: null,
      leaseExpiresAt: null,
      heartbeatAt: null,
    })
    expect(refreshedRun?.finishedAt?.toISOString()).toBe(finishedAt.toISOString())

    const refreshedStep = await prisma.graphStep.findUnique({
      where: {
        runId_stepKey: {
          runId: run.id,
          stepKey: 'screenplay_clip-1',
        },
      },
    })
    expect(refreshedStep).toMatchObject({
      status: RUN_STEP_STATUS.FAILED,
      lastErrorCode: 'WATCHDOG_TIMEOUT',
      lastErrorMessage: 'Task heartbeat timeout',
    })
    expect(refreshedStep?.finishedAt?.toISOString()).toBe(finishedAt.toISOString())
  })
})
