import { describe, expect, it } from 'vitest'
import { deliveryLagLabel, parseCiDeliveryData, watcherFailureLabel } from './ci-delivery-data'

const observedAt = '2026-08-12T10:00:00.000Z'
const progressAt = '2026-08-12T09:58:00.000Z'
const UNKNOWN_IDENTITY = { servedSha: null, mainSha: null, commitsBehind: null, mainCommitAt: null }

function completePayload(overrides: Record<string, unknown> = {}) {
  return {
    state: 'running',
    complete: false,
    observedAt,
    queue: {
      requests: [{ id: 'req-100-example', observedAt }],
      depth: 2,
      oldestAgeMs: 40_000,
    },
    operation: null,
    holder: 'pid:1234',
    latestEvent: { at: progressAt, type: 'progress', detail: 'building web' },
    latestProgressAt: progressAt,
    reason: 'active deploy has no authoritative operation identity or lifecycle record',
    servedSha: null,
    mainSha: null,
    commitsBehind: null,
    mainCommitAt: null,
    ...overrides,
  }
}

describe('parseCiDeliveryData', () => {
  it('preserves a collector status without inventing fields', () => {
    expect(parseCiDeliveryData(completePayload(), 'fresh')).toEqual({
      status: 'running',
      completeness: 'incomplete',
      freshness: 'fresh',
      observedAt,
      queue: {
        requests: [{ id: 'req-100-example', observedAt }],
        depth: 2,
        oldestAgeMs: 40_000,
      },
      current: { operation: null, holder: 'pid:1234' },
      latestEvent: { at: progressAt, type: 'progress', detail: 'building web' },
      latestProgressAt: progressAt,
      reason: 'active deploy has no authoritative operation identity or lifecycle record',
      identity: UNKNOWN_IDENTITY,
      watcher: null,
    })
  })

  it('preserves the served-vs-main identity when the collector reports one', () => {
    const served = 'a'.repeat(40)
    const main = 'b'.repeat(40)
    expect(parseCiDeliveryData(completePayload({
      servedSha: served,
      mainSha: main,
      commitsBehind: 3,
      mainCommitAt: progressAt,
    }))).toMatchObject({
      identity: { servedSha: served, mainSha: main, commitsBehind: 3, mainCommitAt: progressAt },
    })
  })

  it('preserves the S3 watcher record when the collector reports one', () => {
    const sha = 'a'.repeat(40)
    expect(parseCiDeliveryData(completePayload({
      watcher: { targetSha: sha, attempts: 2, lastStatus: 'deploy-clone-dirty', lastDetail: 'local changes', lastAt: observedAt, lastOk: false },
    }))).toMatchObject({
      watcher: { targetSha: sha, attempts: 2, lastStatus: 'deploy-clone-dirty', lastDetail: 'local changes', lastAt: observedAt, lastOk: false },
    })
  })

  it('treats an absent watcher field the same as null (older collector build)', () => {
    expect(parseCiDeliveryData(completePayload())).toMatchObject({ watcher: null })
  })

  it('keeps an explicit idle snapshot complete when queue and current operation are absent', () => {
    expect(parseCiDeliveryData({
      state: 'idle',
      complete: true,
      observedAt,
      queue: { requests: [], depth: 0, oldestAgeMs: null },
      operation: null,
      holder: null,
      latestEvent: null,
      latestProgressAt: null,
      reason: null,
      servedSha: null,
      mainSha: null,
      commitsBehind: null,
      mainCommitAt: null,
    })).toMatchObject({
      status: 'idle',
      completeness: 'complete',
      queue: { requests: [], depth: 0, oldestAgeMs: null },
      current: null,
    })
  })

  it('returns explicit unknown for missing collector data', () => {
    expect(parseCiDeliveryData(undefined)).toEqual({
      status: 'unknown',
      completeness: 'incomplete',
      freshness: 'unknown',
      observedAt: null,
      queue: null,
      current: null,
      latestEvent: null,
      latestProgressAt: null,
      reason: 'deploy-status data unavailable',
      identity: UNKNOWN_IDENTITY,
      watcher: null,
    })
  })

  it('returns explicit unknown for malformed collector data', () => {
    expect(parseCiDeliveryData({ state: 'running', complete: true, observedAt: 'not-a-timestamp', operation: null, holder: null, latestProgressAt: null, latestEvent: null, reason: null, queue: null })).toEqual({
      status: 'unknown',
      completeness: 'incomplete',
      freshness: 'unknown',
      observedAt: null,
      queue: null,
      current: null,
      latestEvent: null,
      latestProgressAt: null,
      reason: 'deploy-status observedAt must be an ISO timestamp',
      identity: UNKNOWN_IDENTITY,
      watcher: null,
    })
  })

  it('preserves an incomplete snapshot without upgrading it to complete', () => {
    expect(parseCiDeliveryData({
      state: 'queued',
      complete: false,
      observedAt,
      queue: { requests: [{ id: 'req-100-example', observedAt }], depth: 1, oldestAgeMs: null },
      operation: null,
      holder: null,
      latestEvent: null,
      latestProgressAt: null,
      reason: 'event source unavailable',
      servedSha: null,
      mainSha: null,
      commitsBehind: null,
      mainCommitAt: null,
    })).toMatchObject({
      status: 'queued',
      completeness: 'incomplete',
      freshness: 'unknown',
      reason: 'event source unavailable',
    })
  })

  it('rejects stalled data with no progress evidence', () => {
    expect(parseCiDeliveryData(completePayload({ state: 'stalled', latestProgressAt: null }))).toEqual({
      status: 'unknown',
      completeness: 'incomplete',
      freshness: 'unknown',
      observedAt: null,
      queue: null,
      current: null,
      latestEvent: null,
      latestProgressAt: null,
      reason: 'stalled deploy-status requires a latest progress timestamp',
      identity: UNKNOWN_IDENTITY,
      watcher: null,
    })
  })
})

describe('deliveryLagLabel', () => {
  const nowMs = Date.parse('2026-08-12T14:00:00.000Z')

  it('reports a clone that has never deployed', () => {
    expect(deliveryLagLabel(UNKNOWN_IDENTITY, nowMs)).toBe('never deployed yet')
  })

  it('reports up to date when served matches main', () => {
    const sha = 'a'.repeat(40)
    expect(deliveryLagLabel({ servedSha: sha, mainSha: sha, commitsBehind: 0, mainCommitAt: null }, nowMs)).toBe('up to date')
  })

  it('reports the change count and how long it has waited, in owner language', () => {
    expect(deliveryLagLabel({
      servedSha: 'a'.repeat(40),
      mainSha: 'b'.repeat(40),
      commitsBehind: 3,
      mainCommitAt: '2026-08-12T10:00:00.000Z',
    }, nowMs)).toBe('3 changes behind, waiting 4 hours')
  })

  it('never claims a lag it cannot see', () => {
    expect(deliveryLagLabel({ servedSha: 'a'.repeat(40), mainSha: null, commitsBehind: null, mainCommitAt: null }, nowMs))
      .toBe("can't tell how far behind — the latest code isn't visible from here")
  })
})

describe('watcherFailureLabel', () => {
  const sha = 'a'.repeat(40)
  const at = '2026-08-15T05:00:00.000Z'
  const base = { targetSha: sha, attempts: 1, lastStatus: 'deployed', lastDetail: '', lastAt: at, lastOk: true, failureClass: 'none' as const, nextRetryAt: null }

  it('is silent when there is no watcher record yet', () => {
    expect(watcherFailureLabel(null)).toBeNull()
  })

  it('is silent on a successful outcome', () => {
    expect(watcherFailureLabel(base)).toBeNull()
  })

  it('is silent on a coalesced deploy — another lane already covers this commit', () => {
    expect(watcherFailureLabel({ ...base, lastStatus: 'deployed-coalesced', lastOk: false })).toBeNull()
  })

  it('is silent on a lock timeout — another deploy is already in flight', () => {
    expect(watcherFailureLabel({ ...base, lastStatus: 'deploy-lock-timeout', lastDetail: 'busy', lastOk: false })).toBeNull()
  })

  it('reports exactly when a transient failure will retry', () => {
    const now = Date.parse('2026-08-15T05:00:00.000Z')
    expect(watcherFailureLabel({
      ...base,
      lastStatus: 'fetch-failed',
      lastDetail: 'network unavailable',
      lastOk: false,
      failureClass: 'transient',
      nextRetryAt: '2026-08-15T05:01:00.000Z',
    }, 3, now)).toBe(`Auto-deploy could not install ${sha.slice(0, 7)}, attempt 1/3 (fetch-failed: network unavailable) — retrying in 1 minute`)
  })

  it('reports a permanent failure as needing a person immediately', () => {
    expect(watcherFailureLabel({ ...base, lastStatus: 'deploy-clone-dirty', lastDetail: 'local changes', lastOk: false, failureClass: 'permanent' }))
      .toBe(`Auto-deploy could not install ${sha.slice(0, 7)} (deploy-clone-dirty: local changes) — needs a person to look`)
  })

  it('reports a transient failure that exhausted retries as stopped', () => {
    expect(watcherFailureLabel({ ...base, attempts: 3, lastStatus: 'smoke-failed-rolled-back', lastDetail: 'bad build', lastOk: false, failureClass: 'transient' }))
      .toBe(`Auto-deploy stopped retrying ${sha.slice(0, 7)} after 3 failed attempts (smoke-failed-rolled-back: bad build) — needs a person to look`)
  })
})
