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

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

  it('invalidates downstream story-to-script steps and artifacts', async () => {
    const user = await createTestUser()
    const run = await prisma.graphRun.create({
      data: {
        userId: user.id,
        projectId: 'project-retry-story',
        episodeId: 'episode-retry-story',
        workflowType: 'story_to_script_run',
        taskType: 'story_to_script_run',
        targetType: 'NovelPromotionEpisode',
        targetId: 'episode-retry-story',
        status: RUN_STATUS.FAILED,
        queuedAt: new Date(),
        startedAt: new Date(),
        finishedAt: new Date(),
      },
    })

    await prisma.graphStep.createMany({
      data: [
        {
          runId: run.id,
          stepKey: 'analyze_characters',
          stepTitle: 'Analyze Characters',
          status: RUN_STEP_STATUS.FAILED,
          currentAttempt: 1,
          stepIndex: 1,
          stepTotal: 5,
          startedAt: new Date(),
          finishedAt: new Date(),
          lastErrorCode: 'STEP_FAILED',
          lastErrorMessage: 'characters failed',
        },
        {
          runId: run.id,
          stepKey: 'analyze_locations',
          stepTitle: 'Analyze Locations',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 2,
          stepTotal: 5,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
        {
          runId: run.id,
          stepKey: 'split_clips',
          stepTitle: 'Split Clips',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 3,
          stepTotal: 5,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
        {
          runId: run.id,
          stepKey: 'screenplay_clip-a',
          stepTitle: 'Screenplay A',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 4,
          stepTotal: 5,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
        {
          runId: run.id,
          stepKey: 'screenplay_clip-b',
          stepTitle: 'Screenplay B',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 5,
          stepTotal: 5,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
      ],
    })

    await prisma.graphArtifact.createMany({
      data: [
        {
          runId: run.id,
          stepKey: 'analyze_characters',
          artifactType: 'analysis.characters',
          refId: 'episode-retry-story',
          payload: { rows: [{ name: 'Hero' }] },
        },
        {
          runId: run.id,
          stepKey: 'analyze_locations',
          artifactType: 'analysis.locations',
          refId: 'episode-retry-story',
          payload: { rows: [{ name: 'City' }] },
        },
        {
          runId: run.id,
          stepKey: 'split_clips',
          artifactType: 'clips',
          refId: 'episode-retry-story',
          payload: { clips: [{ id: 'clip-a' }] },
        },
        {
          runId: run.id,
          stepKey: 'screenplay_clip-a',
          artifactType: 'screenplay.clip',
          refId: 'clip-a',
          payload: { scenes: [{ id: 1 }] },
        },
      ],
    })

    const retried = await retryFailedStep({
      runId: run.id,
      userId: user.id,
      stepKey: 'analyze_characters',
    })

    expect(retried?.retryAttempt).toBe(2)
    expect(retried?.invalidatedStepKeys.slice().sort()).toEqual([
      'analyze_characters',
      'screenplay_clip-a',
      'screenplay_clip-b',
      'split_clips',
    ])

    const steps = await prisma.graphStep.findMany({
      where: { runId: run.id },
      orderBy: { stepIndex: 'asc' },
    })
    const stepMap = new Map(steps.map((step) => [step.stepKey, step]))
    expect(stepMap.get('analyze_characters')).toMatchObject({
      status: RUN_STEP_STATUS.PENDING,
      currentAttempt: 2,
      lastErrorCode: null,
      lastErrorMessage: null,
    })
    expect(stepMap.get('split_clips')).toMatchObject({
      status: RUN_STEP_STATUS.PENDING,
      currentAttempt: 0,
    })
    expect(stepMap.get('screenplay_clip-a')).toMatchObject({
      status: RUN_STEP_STATUS.PENDING,
      currentAttempt: 0,
    })
    expect(stepMap.get('analyze_locations')).toMatchObject({
      status: RUN_STEP_STATUS.COMPLETED,
      currentAttempt: 1,
    })

    const artifacts = await prisma.graphArtifact.findMany({
      where: { runId: run.id },
      orderBy: { stepKey: 'asc' },
    })
    expect(artifacts.map((artifact) => artifact.stepKey)).toEqual(['analyze_locations'])

    const refreshedRun = await prisma.graphRun.findUnique({ where: { id: run.id } })
    expect(refreshedRun?.status).toBe(RUN_STATUS.RUNNING)
    expect(refreshedRun?.errorCode).toBeNull()
    expect(refreshedRun?.errorMessage).toBeNull()
  })

  it('invalidates only the dependent storyboard branch plus voice analyze', async () => {
    const user = await createTestUser()
    const run = await prisma.graphRun.create({
      data: {
        userId: user.id,
        projectId: 'project-retry-storyboard',
        episodeId: 'episode-retry-storyboard',
        workflowType: 'script_to_storyboard_run',
        taskType: 'script_to_storyboard_run',
        targetType: 'NovelPromotionEpisode',
        targetId: 'episode-retry-storyboard',
        status: RUN_STATUS.FAILED,
        queuedAt: new Date(),
        startedAt: new Date(),
        finishedAt: new Date(),
      },
    })

    await prisma.graphStep.createMany({
      data: [
        {
          runId: run.id,
          stepKey: 'clip_clip-1_phase1',
          stepTitle: 'Clip 1 Phase 1',
          status: RUN_STEP_STATUS.FAILED,
          currentAttempt: 1,
          stepIndex: 1,
          stepTotal: 6,
          startedAt: new Date(),
          finishedAt: new Date(),
          lastErrorCode: 'STEP_FAILED',
          lastErrorMessage: 'phase1 failed',
        },
        {
          runId: run.id,
          stepKey: 'clip_clip-1_phase2_cinematography',
          stepTitle: 'Clip 1 Phase 2 Cine',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 2,
          stepTotal: 6,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
        {
          runId: run.id,
          stepKey: 'clip_clip-1_phase2_acting',
          stepTitle: 'Clip 1 Phase 2 Acting',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 3,
          stepTotal: 6,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
        {
          runId: run.id,
          stepKey: 'clip_clip-1_phase3_detail',
          stepTitle: 'Clip 1 Phase 3',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 4,
          stepTotal: 6,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
        {
          runId: run.id,
          stepKey: 'clip_clip-2_phase3_detail',
          stepTitle: 'Clip 2 Phase 3',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 5,
          stepTotal: 6,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
        {
          runId: run.id,
          stepKey: 'voice_analyze',
          stepTitle: 'Voice Analyze',
          status: RUN_STEP_STATUS.COMPLETED,
          currentAttempt: 1,
          stepIndex: 6,
          stepTotal: 6,
          startedAt: new Date(),
          finishedAt: new Date(),
        },
      ],
    })

    await prisma.graphArtifact.createMany({
      data: [
        {
          runId: run.id,
          stepKey: 'clip_clip-1_phase1',
          artifactType: 'storyboard.clip.phase1',
          refId: 'clip-1',
          payload: { panels: [] },
        },
        {
          runId: run.id,
          stepKey: 'clip_clip-1_phase2_cinematography',
          artifactType: 'storyboard.clip.phase2.cine',
          refId: 'clip-1',
          payload: { rules: [] },
        },
        {
          runId: run.id,
          stepKey: 'clip_clip-2_phase3_detail',
          artifactType: 'storyboard.clip.phase3',
          refId: 'clip-2',
          payload: { panels: [] },
        },
        {
          runId: run.id,
          stepKey: 'voice_analyze',
          artifactType: 'voice.lines',
          refId: 'episode-retry-storyboard',
          payload: { lines: [] },
        },
      ],
    })

    const retried = await retryFailedStep({
      runId: run.id,
      userId: user.id,
      stepKey: 'clip_clip-1_phase1',
    })

    expect(retried?.retryAttempt).toBe(2)
    expect(retried?.invalidatedStepKeys.slice().sort()).toEqual([
      'clip_clip-1_phase1',
      'clip_clip-1_phase2_acting',
      'clip_clip-1_phase2_cinematography',
      'clip_clip-1_phase3_detail',
      'voice_analyze',
    ])

    const steps = await prisma.graphStep.findMany({
      where: { runId: run.id },
      orderBy: { stepIndex: 'asc' },
    })
    const stepMap = new Map(steps.map((step) => [step.stepKey, step]))
    expect(stepMap.get('clip_clip-1_phase1')).toMatchObject({
      status: RUN_STEP_STATUS.PENDING,
      currentAttempt: 2,
    })
    expect(stepMap.get('clip_clip-1_phase2_cinematography')).toMatchObject({
      status: RUN_STEP_STATUS.PENDING,
      currentAttempt: 0,
    })
    expect(stepMap.get('voice_analyze')).toMatchObject({
      status: RUN_STEP_STATUS.PENDING,
      currentAttempt: 0,
    })
    expect(stepMap.get('clip_clip-2_phase3_detail')).toMatchObject({
      status: RUN_STEP_STATUS.COMPLETED,
      currentAttempt: 1,
    })

    const artifacts = await prisma.graphArtifact.findMany({
      where: { runId: run.id },
      orderBy: { stepKey: 'asc' },
    })
    expect(artifacts.map((artifact) => artifact.stepKey)).toEqual(['clip_clip-2_phase3_detail'])
  })
})
