import { describe, expect, it, vi } from 'vitest'
import {
  DEFAULT_CERT_RENEW_WINDOW_MS,
  certIsFresh,
  frpcProxyStarted,
  probeLoopbackBaseApp,
  renderHealthStatus,
  runHealthProbe,
  startHealthMonitor,
  type HealthProbeSeams,
  type HealthReport,
  type IntervalTimer,
} from '../src/health/probe.js'

/** All-passing seams; each test overrides exactly one to prove sub-check independence. */
function healthySeams(over: Partial<HealthProbeSeams> = {}): HealthProbeSeams {
  return {
    isFrpcAlive: () => true,
    probeBaseApp: async () => true,
    readFrpcLog: () => 'proxy [web-terminal] start proxy success',
    certNotAfter: () => new Date('2026-08-01T00:00:00Z'),
    now: () => new Date('2026-07-08T00:00:00Z'),
    ...over,
  }
}

describe('frpcProxyStarted (log scan)', () => {
  it('detects the frpc start-proxy-success line', () => {
    expect(frpcProxyStarted('2026/07/08 [I] [proxy_manager] start proxy success')).toBe(true)
  })

  it('is false before frpc reports success', () => {
    expect(frpcProxyStarted('login to server success\nstart proxy ...')).toBe(false)
    expect(frpcProxyStarted('')).toBe(false)
  })
})

describe('certIsFresh (near-expiry check)', () => {
  const now = new Date('2026-07-08T00:00:00Z')

  it('is fresh when notAfter is beyond the renewal window', () => {
    const notAfter = new Date(now.getTime() + DEFAULT_CERT_RENEW_WINDOW_MS + 60_000)
    expect(certIsFresh(notAfter, now, DEFAULT_CERT_RENEW_WINDOW_MS)).toBe(true)
  })

  it('is NOT fresh when notAfter is inside the renewal window', () => {
    const notAfter = new Date(now.getTime() + DEFAULT_CERT_RENEW_WINDOW_MS - 60_000)
    expect(certIsFresh(notAfter, now, DEFAULT_CERT_RENEW_WINDOW_MS)).toBe(false)
  })

  it('treats a missing cert (null notAfter) as not fresh', () => {
    expect(certIsFresh(null, now, DEFAULT_CERT_RENEW_WINDOW_MS)).toBe(false)
  })
})

describe('probeLoopbackBaseApp (loopback-only)', () => {
  it('targets 127.0.0.1:PORT and returns true on an ok response', async () => {
    const fetchImpl = vi.fn(async (url: string) => ({ ok: url.includes('127.0.0.1:3000') }))
    await expect(probeLoopbackBaseApp(3000, fetchImpl)).resolves.toBe(true)
    expect(fetchImpl).toHaveBeenCalledWith('http://127.0.0.1:3000/')
  })

  it('returns false on a non-ok response', async () => {
    await expect(probeLoopbackBaseApp(3000, async () => ({ ok: false }))).resolves.toBe(false)
  })

  it('swallows a rejected fetch (a probe never throws)', async () => {
    await expect(
      probeLoopbackBaseApp(3000, async () => {
        throw new Error('ECONNREFUSED')
      }),
    ).resolves.toBe(false)
  })

  it('rejects an out-of-range port without fetching', async () => {
    const fetchImpl = vi.fn(async () => ({ ok: true }))
    await expect(probeLoopbackBaseApp(0, fetchImpl)).resolves.toBe(false)
    await expect(probeLoopbackBaseApp(70000, fetchImpl)).resolves.toBe(false)
    expect(fetchImpl).not.toHaveBeenCalled()
  })
})

describe('runHealthProbe (aggregate verdict)', () => {
  it('is healthy when all four sub-checks pass', async () => {
    const report = await runHealthProbe(healthySeams())
    expect(report).toEqual<HealthReport>({
      frpcAlive: true,
      baseAppReachable: true,
      proxyStarted: true,
      certFresh: true,
      healthy: true,
    })
  })

  it('is unhealthy if frpc is dead', async () => {
    const report = await runHealthProbe(healthySeams({ isFrpcAlive: () => false }))
    expect(report.frpcAlive).toBe(false)
    expect(report.healthy).toBe(false)
  })

  it('is unhealthy if the base app is unreachable', async () => {
    const report = await runHealthProbe(healthySeams({ probeBaseApp: async () => false }))
    expect(report.baseAppReachable).toBe(false)
    expect(report.healthy).toBe(false)
  })

  it('is unhealthy if the proxy never started', async () => {
    const report = await runHealthProbe(healthySeams({ readFrpcLog: () => 'connecting...' }))
    expect(report.proxyStarted).toBe(false)
    expect(report.healthy).toBe(false)
  })

  it('is unhealthy if the cert is near expiry', async () => {
    const report = await runHealthProbe(
      healthySeams({
        certNotAfter: () => new Date('2026-07-08T01:00:00Z'), // 1h out, inside 8h window
      }),
    )
    expect(report.certFresh).toBe(false)
    expect(report.healthy).toBe(false)
  })
})

describe('renderHealthStatus (INV9 — non-secret only)', () => {
  const report: HealthReport = {
    frpcAlive: true,
    baseAppReachable: true,
    proxyStarted: true,
    certFresh: true,
    healthy: true,
  }

  it('prints subdomain, host id, expiry date, and flags', () => {
    const lines = renderHealthStatus(
      { subdomain: 'alice', hostId: 'h-1', certNotAfter: new Date('2026-08-01T00:00:00Z') },
      report,
    )
    const joined = lines.join('\n')
    expect(joined).toContain('subdomain: alice')
    expect(joined).toContain('host_id: h-1')
    expect(joined).toContain('cert_expiry: 2026-08-01T00:00:00.000Z')
    expect(joined).toContain('healthy: true')
  })

  it('leaks NO key/cert/token/CSR material', () => {
    const lines = renderHealthStatus(
      { subdomain: 'alice', hostId: 'h-1', certNotAfter: new Date('2026-08-01T00:00:00Z') },
      report,
    )
    const joined = lines.join('\n')
    expect(joined).not.toMatch(/PRIVATE KEY|BEGIN CERTIFICATE|BEGIN CERTIFICATE REQUEST/)
    expect(joined.toLowerCase()).not.toMatch(/token|secret|csr|pem/)
  })

  it('renders (none)/(unknown) placeholders when identifiers are absent', () => {
    const lines = renderHealthStatus({ subdomain: null, hostId: null, certNotAfter: null }, report)
    const joined = lines.join('\n')
    expect(joined).toContain('subdomain: (none)')
    expect(joined).toContain('cert_expiry: (unknown)')
  })
})

describe('startHealthMonitor (periodic)', () => {
  function fakeTimer(): { timer: IntervalTimer; fire: () => void; cleared: boolean } {
    let cb: (() => void) | null = null
    const state = { cleared: false }
    return {
      timer: {
        setInterval: (fn) => {
          cb = fn
          return 1
        },
        clearInterval: () => {
          state.cleared = true
        },
      },
      fire: () => cb?.(),
      get cleared() {
        return state.cleared
      },
    }
  }

  it('runs the probe on each tick and reports it', async () => {
    const report: HealthReport = {
      frpcAlive: true,
      baseAppReachable: true,
      proxyStarted: true,
      certFresh: true,
      healthy: true,
    }
    const probe = vi.fn(async () => report)
    const seen: HealthReport[] = []
    const ft = fakeTimer()
    const monitor = startHealthMonitor(probe, (r) => seen.push(r), { timer: ft.timer })

    ft.fire()
    await Promise.resolve()
    await Promise.resolve()
    expect(probe).toHaveBeenCalledTimes(1)
    expect(seen).toEqual([report])

    monitor.stop()
    expect(ft.cleared).toBe(true)
  })

  it('swallows a rejected probe (monitor never crashes)', async () => {
    const ft = fakeTimer()
    const onReport = vi.fn()
    startHealthMonitor(async () => Promise.reject(new Error('boom')), onReport, { timer: ft.timer })
    ft.fire()
    await Promise.resolve()
    await Promise.resolve()
    expect(onReport).not.toHaveBeenCalled()
  })
})
