import { describe, expect, it } from 'vitest'
import type { HarnessDecision, HarnessEvent } from '../../lib/harness-types'
import { authoritativeDecisionDetail, eventAcceptance, fixLoopShare, hasJournaledExecutingAccount, ratelimitData, runControlAvailability, taskFailureDetail, taskFailureReasons } from './PlanRunApp'

describe('run observability projections', () => {
  const events: HarnessEvent[] = [
    { id: 'a', source: 'runlog', kind: 'llm-fixer', ts: '2026-07-22T00:00:00.000Z', payload: { durationMs: 25 } },
    { id: 'b', source: 'runlog', kind: 'gate0', ts: '2026-07-22T00:01:00.000Z', payload: { durationMs: 75, acceptance: { id: 'a1', text: 'passes', result: 'passed' } } },
  ]

  it('keeps acceptance and fix-loop values authoritative', () => {
    expect(eventAcceptance(events)).toEqual([{ id: 'a1', text: 'passes', result: 'passed' }])
    expect(fixLoopShare(events)).toEqual({ percent: 25 })
  })

  it('preserves enriched decision option meaning', () => {
    const decision: HarnessDecision = {
      id: 'd1', task: 't1', summary: 'choose', options: [{ value: 'proceed', meaning: 'continue safely' }], requestedAt: null,
      needs: 'approval', why: 'gate failed', blast_radius: 'one run',
    }
    expect(authoritativeDecisionDetail(decision)).toEqual({
      taskId: 't1', needs: 'approval', why: 'gate failed', blastRadius: 'one run', options: ['proceed — continue safely'],
    })
  })

  it('enables only state-valid run controls', () => {
    expect(runControlAvailability('running')).toEqual({ pause: true, resume: false, kill: true })
    expect(runControlAvailability('paused-draining')).toEqual({ pause: false, resume: true, kill: true })
    expect(runControlAvailability('paused')).toEqual({ pause: false, resume: true, kill: true })
    expect(runControlAvailability('succeeded')).toEqual({ pause: false, resume: false, kill: false })
    expect(runControlAvailability('unknown')).toEqual({ pause: false, resume: false, kill: false })
  })

  it('renders real rate-limit data without inferred fields', () => {
    const data = ratelimitData({ run: { ratelimits: { openai: { provider: 'openai', state: 'wait', wrapper: 'codex', task: 't1', resumeAt: '2026-07-22T00:01:00.000Z' } } } })
    expect(data.wrappers).toEqual(['codex'])
    expect(data.limits[0]).toMatchObject({ wrapper: 'codex', active: true, parkedTasks: ['t1'] })
  })

  it('keeps A9 Executing account gap when no journaled account exists', () => {
    expect(hasJournaledExecutingAccount([])).toBe(false)
    expect(hasJournaledExecutingAccount([
      {
        id: '1',
        source: 'runlog',
        kind: 'dispatch.attempt',
        ts: '2026-07-22T00:00:00.000Z',
        taskId: 't1',
        payload: { seat: 'coder' },
      },
    ])).toBe(false)
  })

  it('clears A9 when at least one dispatch journals a truthful account', () => {
    expect(hasJournaledExecutingAccount([
      {
        id: '1',
        source: 'runlog',
        kind: 'dispatch.attempt',
        ts: '2026-07-22T00:00:00.000Z',
        taskId: 't1',
        payload: { seat: 'coder', account: 'zync' },
      },
    ])).toBe(true)
  })

  it('clears A9 when attempt.reply journals a wrapper-reported account', () => {
    expect(hasJournaledExecutingAccount([
      {
        id: '1',
        source: 'runlog',
        kind: 'attempt.reply',
        ts: '2026-07-22T00:00:00.000Z',
        taskId: 't1',
        payload: { account: 'seat-runner@example.com', valid: true },
      },
    ])).toBe(true)
  })

  it('keeps A9 when attempt.reply has no account', () => {
    expect(hasJournaledExecutingAccount([
      {
        id: '1',
        source: 'runlog',
        kind: 'attempt.reply',
        ts: '2026-07-22T00:00:00.000Z',
        taskId: 't1',
        payload: { valid: true },
      },
    ])).toBe(false)
  })
})

describe('taskFailureReasons', () => {
  it('surfaces a quarantine reason for the task', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'quarantine', ts: '2026-07-22T00:00:00.000Z', taskId: 't1', payload: { reason: 'flaky infra' } },
    ]
    const reasons = taskFailureReasons(events)
    expect(reasons.get('t1')).toMatchObject({ taskId: 't1', quarantineReason: 'flaky infra' })
  })

  it('maps review.verdict FAIL findings null-safely and drops non-object entries', () => {
    const events: HarnessEvent[] = [
      {
        id: 'e1', source: 'runlog', kind: 'review.verdict', ts: '2026-07-22T00:00:00.000Z', taskId: 't1',
        payload: {
          verdict: 'FAIL',
          findings: [
            { severity: 'high', path: 'src/a.ts', message: 'bug' },
            { severity: null, path: 'src/b.ts', message: null },
            'not-an-object',
            42,
          ],
        },
      },
    ]
    const reasons = taskFailureReasons(events)
    expect(reasons.get('t1')?.findings).toEqual([
      { severity: 'high', path: 'src/a.ts', message: 'bug' },
      { severity: null, path: 'src/b.ts', message: null },
    ])
  })

  it('adds nothing for a PASS verdict', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'review.verdict', ts: '2026-07-22T00:00:00.000Z', taskId: 't1', payload: { verdict: 'PASS', findings: [{ severity: 'low', path: 'x', message: 'y' }] } },
    ]
    expect(taskFailureReasons(events).has('t1')).toBe(false)
  })

  it('surfaces the verify.failed command', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'verify.failed', ts: '2026-07-22T00:00:00.000Z', taskId: 't1', payload: { command: 'pnpm test' } },
    ]
    expect(taskFailureReasons(events).get('t1')).toMatchObject({ failedCommand: 'pnpm test' })
  })

  it('maps task-end failure to failClass and cause', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'task-end', ts: '2026-07-22T00:00:00.000Z', taskId: 't1', payload: { failure: { failureClass: 'timeout', cause: 'exceeded 30m' } } },
    ]
    expect(taskFailureReasons(events).get('t1')).toMatchObject({ failClass: 'timeout', cause: 'exceeded 30m' })
  })

  it('adds nothing for task-end without a failure', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'task-end', ts: '2026-07-22T00:00:00.000Z', taskId: 't1', payload: {} },
    ]
    expect(taskFailureReasons(events).has('t1')).toBe(false)
  })

  it('clears an accumulated reason on task-start (retry resets)', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'quarantine', ts: '2026-07-22T00:00:00.000Z', taskId: 't1', payload: { reason: 'first try' } },
      { id: 'e2', source: 'runlog', kind: 'task-start', ts: '2026-07-22T00:01:00.000Z', taskId: 't1', payload: {} },
    ]
    expect(taskFailureReasons(events).has('t1')).toBe(false)
  })

  it('ignores events with no task identity', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'quarantine', ts: '2026-07-22T00:00:00.000Z', payload: { reason: 'orphan' } },
    ]
    expect(taskFailureReasons(events).size).toBe(0)
  })

  it('falls back to payload.task when taskId is absent, and merges later events last-wins per field', () => {
    const events: HarnessEvent[] = [
      { id: 'e1', source: 'runlog', kind: 'quarantine', ts: '2026-07-22T00:00:00.000Z', payload: { task: 't1', reason: 'stale run' } },
      { id: 'e2', source: 'runlog', kind: 'quarantine', ts: '2026-07-22T00:01:00.000Z', payload: { task: 't1', reason: 'newer reason' } },
    ]
    const reasons = taskFailureReasons(events)
    expect(reasons.get('t1')).toMatchObject({ quarantineReason: 'newer reason' })
  })
})

describe('taskFailureDetail', () => {
  it('joins all lines in order: quarantine, cause, verify command, then findings', () => {
    const reasons = taskFailureReasons([
      { id: 'e1', source: 'runlog', kind: 'quarantine', ts: '2026-07-22T00:00:00.000Z', taskId: 't1', payload: { reason: 'flaky infra' } },
      { id: 'e2', source: 'runlog', kind: 'task-end', ts: '2026-07-22T00:01:00.000Z', taskId: 't1', payload: { failure: { failureClass: 'timeout', cause: 'exceeded 30m' } } },
      { id: 'e3', source: 'runlog', kind: 'verify.failed', ts: '2026-07-22T00:02:00.000Z', taskId: 't1', payload: { command: 'pnpm test' } },
      {
        id: 'e4', source: 'runlog', kind: 'review.verdict', ts: '2026-07-22T00:03:00.000Z', taskId: 't1',
        payload: { verdict: 'FAIL', findings: [{ severity: 'high', path: 'src/a.ts', message: 'bug' }, { severity: null, path: 'src/b.ts', message: null }] },
      },
    ])
    const detail = taskFailureDetail(reasons.get('t1')!)
    expect(detail).toBe([
      'Quarantined: flaky infra',
      'exceeded 30m',
      'Verify failed: pnpm test',
      'high · src/a.ts · bug',
      'src/b.ts',
    ].join('\n'))
  })

  it('returns an empty string for an empty reason', () => {
    expect(taskFailureDetail({ taskId: 't1', findings: [] })).toBe('')
  })
})
