import { describe, it, expect } from 'vitest'
import { deriveVerdict, sectionsForType, isObservable, checkLabel, nomosPrompt } from './entityView'
import type { Check, Entity } from './api'

function entity(partial: Partial<Entity> = {}): Entity {
  return {
    id: 'e1',
    slug: 'host:strong',
    type: 'proxmox-host',
    name: 'strong',
    attributes: {},
    version: 1,
    created_at: '',
    updated_at: '',
    ...partial
  } as Entity
}

function check(partial: Partial<Check> = {}): Check {
  return {
    id: 'c' + Math.random(),
    slug: 'check:x',
    kind: 'ssh-script',
    interval_s: 60,
    timeout_s: 30,
    enabled: true,
    version: 1,
    ...partial
  } as Check
}

describe('deriveVerdict', () => {
  // The case that motivated the redesign: host:strong reads down because one
  // ping probe fails from the scheduler's network vantage point, while five
  // ssh checks pass. The window must say that, not just "down".
  it('names the failing probe rather than counting', () => {
    const v = deriveVerdict(entity({ health: 'down' }), [
      check({ kind: 'ping', last_health: 'down' }),
      check({ config: { script: 'cpu_check.sh' }, last_health: 'healthy' }),
      check({ config: { script: 'memory_check.sh' }, last_health: 'healthy' }),
      check({ config: { script: 'load_check.sh' }, last_health: 'healthy' }),
      check({ config: { script: 'disk_usage_check.sh' }, last_health: 'healthy' }),
      check({ config: { script: 'updates_check.sh' }, last_health: 'healthy' })
    ])
    expect(v.health).toBe('down')
    expect(v.reason).toContain('ping')
    expect(v.reason).toContain('5 of 6 checks passing')
    expect(v.failing).toHaveLength(1)
  })

  it('says nothing when everything passes', () => {
    const v = deriveVerdict(entity({ health: 'healthy' }), [
      check({ last_health: 'healthy' }),
      check({ last_health: 'healthy' })
    ])
    expect(v.reason).toBe('')
    expect(v.passing).toBe(2)
  })

  // "No checks" is a coverage gap, not good news — calling it healthy would be
  // a lie by omission, and it is exactly what the unmonitored signal reports.
  it('distinguishes unmonitored from healthy', () => {
    const v = deriveVerdict(entity(), [])
    expect(v.health).toBe('unmonitored')
    expect(v.reason).toContain('no checks')
  })

  it('ignores disabled checks', () => {
    const v = deriveVerdict(entity({ health: 'healthy' }), [
      check({ last_health: 'healthy' }),
      check({ kind: 'ping', last_health: 'down', enabled: false })
    ])
    expect(v.failing).toHaveLength(0)
    expect(v.total).toBe(1)
  })

  it('reports checks that have not run yet without calling them failures', () => {
    const v = deriveVerdict(entity({ health: 'healthy' }), [
      check({ last_health: 'healthy' }),
      check({ config: { script: 'updates_check.sh' } }) // never run
    ])
    expect(v.failing).toHaveLength(0)
    expect(v.reason).toContain('not yet run')
  })

  it('orders failures worst-first so the reason leads with the worst', () => {
    const v = deriveVerdict(entity({ health: 'down' }), [
      check({ config: { script: 'disk_usage_check.sh' }, last_health: 'degraded' }),
      check({ kind: 'ping', last_health: 'down' })
    ])
    expect(v.failing[0].kind).toBe('ping')
    expect(v.reason.startsWith('ping')).toBe(true)
  })

  it('survives a null entity', () => {
    expect(deriveVerdict(null, []).health).toBe('unknown')
  })
})

describe('checkLabel', () => {
  it('prefers the script name over the generic kind', () => {
    expect(checkLabel(check({ config: { script: 'cpu_check.sh' } }))).toBe('cpu_check')
    expect(checkLabel(check({ kind: 'ping' }))).toBe('ping')
  })
})

describe('sectionsForType', () => {
  it('gives infrastructure the triage ordering, status first', () => {
    expect(sectionsForType('proxmox-host')[0]).toBe('status')
    expect(sectionsForType('service')).toContain('impact')
  })

  // A document has no checks or metrics; offering those sections is noise.
  it('leads knowledge types with their content and offers no monitoring', () => {
    const s = sectionsForType('document')
    expect(s[0]).toBe('content')
    expect(s).not.toContain('status')
    expect(s).not.toContain('metrics')
  })

  it('gives record types a minimal view', () => {
    expect(sectionsForType('execution')).not.toContain('status')
    expect(sectionsForType('signal')).not.toContain('metrics')
  })

  // A new entity type must never produce a blank window.
  it('falls back to infrastructure for unknown types', () => {
    expect(sectionsForType('something-new')).toEqual(sectionsForType('lxc'))
  })
})

describe('isObservable', () => {
  it('separates things that can be probed from records and documents', () => {
    expect(isObservable('lxc')).toBe(true)
    expect(isObservable('document')).toBe(false)
    expect(isObservable('execution')).toBe(false)
  })
})

describe('nomosPrompt', () => {
  it('scopes the task to the failure when there is one', () => {
    const v = deriveVerdict(entity({ health: 'down' }), [
      check({ kind: 'ping', last_health: 'down' }),
      check({ last_health: 'healthy' })
    ])
    const p = nomosPrompt(entity({ health: 'down' }), v)
    expect(p).toContain('host:strong')
    expect(p).toContain('ping')
  })

  it('asks about coverage when nothing is monitored', () => {
    const v = deriveVerdict(entity(), [])
    expect(nomosPrompt(entity(), v)).toContain('no checks')
  })
})
