/** @vitest-environment jsdom */
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom/vitest'
import React from 'react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { ObservabilityReport } from '@overdeck/report-contract'
import { ObservabilityReportTimeoutError } from '../../lib/collector-client'
import { ReportsContent } from './ReportsContent'

const useObservabilityReport = vi.hoisted(() => vi.fn())
const useActivitySourceEntries = vi.hoisted(() => vi.fn())
vi.mock('../../lib/collector-queries', () => ({ useObservabilityReport, useActivitySourceEntries }))

const REPORT: ObservabilityReport = {
  schemaVersion: 1,
  query: {
    from: '2026-08-15T12:00:00.000Z',
    to: '2026-08-16T12:00:00.000Z',
    timezone: 'UTC',
  },
  generatedAt: '2026-08-16T12:00:05.000Z',
  coverage: {
    status: 'partial',
    from: '2026-08-15T12:00:00.000Z',
    to: '2026-08-16T12:00:00.000Z',
    generatedAt: '2026-08-16T12:00:05.000Z',
    sources: [],
    gaps: [{ domain: 'activity', sourceId: 'controller', reason: 'Controller history starts after this report begins.' }],
  },
  sections: [{
    kind: 'activity',
    status: 'partial',
    title: 'Activity and source trust',
    metrics: [
      { id: 'recorded-events', label: 'Recorded events', value: 42, unit: 'count', coverage: 'partial' },
      { id: 'failures', label: 'Failures', value: 3, unit: 'count', coverage: 'partial' },
      { id: 'active-sources', label: 'Readable sources', value: 1, numerator: 1, denominator: 2, unit: 'count', coverage: 'complete' },
      { id: 'source-problems', label: 'Source problems', value: 1, unit: 'count', coverage: 'complete' },
    ],
    series: [{ id: 'activity-volume', label: 'Recorded activity', unit: 'count', coverage: 'partial', points: [{ ts: '2026-08-16T11:00:00.000Z', value: 4 }], gaps: [] }],
    categoryBreakdown: [{ category: 'service', count: 42 }],
    attention: [{
      id: 'actions:1',
      kind: 'event',
      ts: '2026-08-16T11:00:00.000Z',
      title: 'Deploy failed',
      category: 'service',
      severity: 'error',
      actor: 'agent',
      sourceId: 'actions',
      project: 'overdeck',
      result: 'failure',
      evidenceQuery: { sourceId: 'actions', recordId: 'actions:1', from: '2026-08-15T12:00:00.000Z', to: '2026-08-16T12:00:00.000Z' },
    }],
    sources: [{
      id: 'actions', label: 'Collector actions', category: 'service', authority: 'authoritative', status: 'ok', storage: 'Local source file', retention: 'Source-managed', queryBounds: 'Requested range', coverageFrom: '2026-08-15T12:00:00.000Z', earliest: '2026-08-15T12:00:00.000Z', latest: '2026-08-16T11:00:00.000Z', matchingRecords: 42, retainedRecords: 80, skipped: 0, href: '/logs/actions?from=x',
    }],
    truncated: false,
    errors: [],
  }, {
    kind: 'execution',
    status: 'partial',
    title: 'Work execution and delivery',
    metrics: [
      { id: 'requests', label: 'Requests in window', value: 2, unit: 'count', coverage: 'complete' },
      { id: 'shipped-requests', label: 'Shipped requests', value: 2, unit: 'count', coverage: 'complete' },
      { id: 'factory-runs', label: 'Factory runs', value: 1, unit: 'count', coverage: 'complete' },
      { id: 'failed-runs', label: 'Failed runs', value: 0, unit: 'count', coverage: 'complete' },
    ],
    requestStates: [{ key: 'shipped', label: 'Shipped', count: 2 }],
    runOutcomes: [{ key: 'succeeded', label: 'Succeeded', count: 1 }],
    failureBreakdown: [],
    durationSeries: [{ ts: '2026-08-16T10:00:00.000Z', value: 3_600_000, runId: 'adw-report' }],
    requests: [{
      id: 'request-report', title: 'Build observability reports', project: 'overdeck', state: 'shipped',
      askedAt: '2026-08-16T08:00:00.000Z', updatedAt: '2026-08-16T11:00:00.000Z', requestHref: '/requests?request=request-report',
      correlation: 'linked', factoryRunId: 'adw-report', factoryRunHref: '/factory/adw-report', proofHref: '/reports',
      delivery: { landedAt: '2026-08-16T10:30:00.000Z', deployedAt: '2026-08-16T11:00:00.000Z', proofRecorded: true },
    }, {
      id: 'request-older', title: 'Ship an older report', project: 'overdeck', state: 'shipped',
      askedAt: '2026-08-15T14:00:00.000Z', updatedAt: '2026-08-15T16:00:00.000Z', requestHref: '/requests?request=request-older',
      correlation: 'unlinked', correlationReason: 'No factory run was explicitly linked to this request.',
      delivery: { proofRecorded: false },
    }],
    runs: [{
      id: 'adw-report', label: 'observability-reports', project: 'overdeck', status: 'success', outcome: 'succeeded',
      startedAt: '2026-08-16T09:00:00.000Z', endedAt: '2026-08-16T10:00:00.000Z', durationMs: 3_600_000,
      attempts: 1, retries: 0, failedGates: 0, toolCalls: 8, changedFiles: 5, insertions: 100, deletions: 10,
      models: ['sonnet'], accounts: ['claude11'], sessions: ['session-1'], hosts: ['debian1'],
      phases: [{ label: 'Build', status: 'completed', durationMs: 3_600_000, attempts: 1, retries: 0, gatePassed: 1, gateFailed: 0, toolCalls: 8 }],
      href: '/factory/adw-report',
    }],
    gaps: [{ domain: 'execution', sourceId: 'request-registry', reason: 'One older request has no explicitly linked factory run.' }],
    truncated: false,
    errors: [],
  }, {
    kind: 'reliability',
    status: 'complete',
    title: 'Reliability and recovery',
    metrics: [
      { id: 'incidents', label: 'Incidents in window', value: 2, unit: 'count', coverage: 'complete' },
      { id: 'unresolved-incidents', label: 'Unresolved incidents', value: 1, unit: 'count', coverage: 'complete' },
      { id: 'resolved-incidents', label: 'Resolved incidents', value: 1, unit: 'count', coverage: 'complete' },
      { id: 'recorded-failures', label: 'Recorded failures', value: 3, unit: 'count', coverage: 'complete' },
    ],
    incidents: [{ id: 'incident-1', title: 'Buildbox unavailable', priority: 'P1', incidentType: 'buildbox', state: 'resolved', createdAt: '2026-08-16T08:00:00.000Z', resolvedAt: '2026-08-16T09:00:00.000Z', durationMs: 3_600_000, ageMs: null, failureClass: 'host-unreachable', href: '/incidents' }],
    failureGroups: [{ key: 'activity:reaper:kill', label: 'Agent reaper failure', source: 'Agent reaper', category: 'service', count: 3, firstAt: '2026-08-16T08:00:00.000Z', latestAt: '2026-08-16T10:00:00.000Z', project: 'overdeck', host: 'debian1', service: 'agent-reaper', href: '/logs/reaper' }],
    resolvedDurationSeries: [{ ts: '2026-08-16T09:00:00.000Z', value: 3_600_000, incidentId: 'incident-1' }],
    projectBreakdown: [{ key: 'overdeck', label: 'overdeck', count: 3 }],
    hostBreakdown: [{ key: 'debian1', label: 'debian1', count: 3 }],
    serviceBreakdown: [{ key: 'agent-reaper', label: 'agent-reaper', count: 3 }],
    gaps: [],
    truncated: false,
    errors: [],
  }, {
    kind: 'capacity',
    status: 'partial',
    title: 'Capacity, accounts, and spend',
    metrics: [
      { id: 'recorded-tokens', label: 'Recorded tokens', value: 120000, unit: 'tokens', coverage: 'complete' },
      { id: 'recorded-cost', label: 'Recorded provider cost', value: 4.5, unit: 'currency', numerator: 2, denominator: 3, coverage: 'partial' },
      { id: 'active-sessions', label: 'Active sessions', value: 5, unit: 'count', coverage: 'complete' },
      { id: 'queued-builds', label: 'Queued builds', value: 2, unit: 'count', coverage: 'complete' },
    ],
    byModel: [{ key: 'sonnet', label: 'sonnet', tokens: 120000, cost: 4.5, attempts: 2, unattributed: false }],
    byAccount: [{ key: 'claude11', label: 'claude11', tokens: 120000, cost: 4.5, attempts: 2, unattributed: false }],
    byHost: [{ key: 'debian1', label: 'debian1', tokens: 120000, cost: 4.5, attempts: 2, unattributed: false }],
    byProject: [{ key: 'overdeck', label: 'overdeck', tokens: 120000, cost: 4.5, attempts: 2, unattributed: false }],
    accounts: [{ id: 'claude:claude11', label: 'Claude · Primary', provider: 'claude', usedPercent: 63, status: 'ok', spendAmount: 18, spendLimit: 50, currency: 'USD', resetAt: '2026-08-20T00:00:00.000Z' }],
    snapshot: { activeSessions: 5, liveAgents: 8, runningBuilds: 1, queuedBuilds: 2, buildWaitP95Ms: 90000 },
    costPerSucceededRun: null,
    gaps: [{ domain: 'capacity', sourceId: 'factory', metric: 'cost', reason: 'One matching attempt has no recorded provider cost.' }],
    truncated: false,
    errors: [],
  }, {
    kind: 'lineage',
    status: 'partial',
    title: 'Delivery lineage',
    lineages: [{
      requestId: 'request-report',
      title: 'Build observability reports',
      project: 'overdeck',
      complete: true,
      stages: [
        { kind: 'request', label: 'Request recorded', status: 'recorded', detail: 'Asked in overdeck.', recordedAt: '2026-08-16T08:00:00.000Z', evidence: { source: 'request-registry', recordId: 'request-report', href: '/requests?request=request-report' } },
        { kind: 'run', label: 'Run recorded', status: 'recorded', detail: 'observability-reports', recordedAt: '2026-08-16T09:00:00.000Z', evidence: { source: 'factory-run', recordId: 'adw-report', href: '/factory/adw-report' } },
        { kind: 'gate', label: 'Checks passed', status: 'recorded', detail: '1 of 1 recorded check results passed.', evidence: { source: 'factory-run', recordId: 'adw-report', href: '/factory/adw-report' } },
        { kind: 'change', label: 'Change recorded', status: 'recorded', detail: '5 changed files explicitly linked to the run.', evidence: { source: 'factory-run', recordId: 'adw-report', href: '/factory/adw-report' } },
        { kind: 'land', label: 'Landed', status: 'recorded', detail: 'A landing receipt is recorded.', recordedAt: '2026-08-16T10:30:00.000Z', evidence: { source: 'request-registry', recordId: 'request-report', href: '/requests?request=request-report' } },
        { kind: 'deploy', label: 'Deployed', status: 'recorded', detail: 'A deployment receipt is recorded.', recordedAt: '2026-08-16T11:00:00.000Z', evidence: { source: 'request-registry', recordId: 'request-report', href: '/requests?request=request-report' } },
        { kind: 'proof', label: 'Owner proof recorded', status: 'recorded', detail: 'The request has an explicit owner-proof link.', evidence: { source: 'request-registry', recordId: 'request-report', href: '/reports' } },
      ],
      gaps: [],
    }, {
      requestId: 'request-older',
      title: 'Ship an older report',
      project: 'overdeck',
      complete: false,
      stages: [
        { kind: 'request', label: 'Request recorded', status: 'recorded', detail: 'Asked in overdeck.', recordedAt: '2026-08-15T14:00:00.000Z', evidence: { source: 'request-registry', recordId: 'request-older', href: '/requests?request=request-older' } },
        { kind: 'run', label: 'Run not linked', status: 'missing', detail: 'No factory run was explicitly linked to this request.' },
        { kind: 'gate', label: 'Checks unavailable', status: 'missing', detail: 'Checks cannot be linked without the explicit run record.' },
        { kind: 'change', label: 'Change unavailable', status: 'missing', detail: 'Changed files cannot be linked without the explicit run record.' },
        { kind: 'land', label: 'Landing not recorded', status: 'missing', detail: 'No landing receipt is recorded for this request.' },
        { kind: 'deploy', label: 'Deployment not recorded', status: 'missing', detail: 'No deployment receipt is recorded for this request.' },
        { kind: 'proof', label: 'Owner proof missing', status: 'missing', detail: 'No owner-proof link is recorded for this request.' },
      ],
      gaps: [
        { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No factory run was explicitly linked to this request.' },
        { domain: 'lineage', sourceId: 'factory-run', reason: 'Ship an older report: Checks cannot be linked without the explicit run record.' },
        { domain: 'lineage', sourceId: 'factory-run', reason: 'Ship an older report: Changed files cannot be linked without the explicit run record.' },
        { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No landing receipt is recorded for this request.' },
        { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No deployment receipt is recorded for this request.' },
        { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No owner-proof link is recorded for this request.' },
      ],
    }],
    gaps: [
      { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No factory run was explicitly linked to this request.' },
      { domain: 'lineage', sourceId: 'factory-run', reason: 'Ship an older report: Checks cannot be linked without the explicit run record.' },
      { domain: 'lineage', sourceId: 'factory-run', reason: 'Ship an older report: Changed files cannot be linked without the explicit run record.' },
      { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No landing receipt is recorded for this request.' },
      { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No deployment receipt is recorded for this request.' },
      { domain: 'lineage', sourceId: 'request-registry', reason: 'Ship an older report: No owner-proof link is recorded for this request.' },
    ],
    truncated: false,
    errors: [],
  }, {
    kind: 'history',
    status: 'partial',
    title: 'Durable history and comparison',
    bucketHours: 1,
    rangeDays: 1,
    retentionDays: 400,
    earliestAvailable: '2026-08-15T12:00:00.000Z',
    metrics: [{
      key: 'activity.recorded-events', label: 'Recorded events', unit: 'count', metricVersion: 1,
      current: 42, previous: 35, delta: 7, direction: 'up', coverage: 'partial', comparisonCompatible: true,
    }, {
      key: 'activity.failures', label: 'Recorded failures', unit: 'count', metricVersion: 1,
      current: 3, previous: null, delta: null, direction: 'unknown', coverage: 'partial', comparisonCompatible: false,
      comparisonReason: 'The preceding period has missing retained history.',
    }],
    series: [{
      key: 'activity.recorded-events', label: 'Recorded events', unit: 'count', metricVersion: 1,
      points: [{ ts: '2026-08-16T09:00:00.000Z', value: 18 }, { ts: '2026-08-16T11:00:00.000Z', value: 24 }],
      gaps: [{ from: '2026-08-16T10:00:00.000Z', to: '2026-08-16T11:00:00.000Z', reason: 'No retained measurement was recorded for this hour.' }],
    }],
    gaps: [{ domain: 'history', metric: 'activity.recorded-events', from: '2026-08-16T10:00:00.000Z', to: '2026-08-16T11:00:00.000Z', reason: 'No retained measurement was recorded for this hour.' }],
    quarantinedRows: 0,
    errors: [],
  }],
  filters: { projects: ['overdeck'], sources: [{ id: 'actions', label: 'Collector actions' }] },
}

afterEach(() => {
  cleanup()
  vi.clearAllMocks()
  vi.restoreAllMocks()
  window.localStorage.clear()
  window.history.replaceState(null, '', '/reports')
})

function setSuccessfulQueries() {
  useObservabilityReport.mockReturnValue({ data: REPORT, isLoading: false, isError: false, isFetching: false, refetch: vi.fn() })
  useActivitySourceEntries.mockReturnValue({
    data: {
      source: {}, total: 1, offset: 0, limit: 50, skipped: [], skippedTotal: 0, skippedTruncated: false,
      events: [{ id: 'actions:1', ts: '2026-08-16T11:00:00.000Z', category: 'service', source: 'actions', actor: 'agent', severity: 'error', title: 'Deploy failed', project: 'overdeck' }],
    },
    isLoading: false, isError: false, isFetching: false, refetch: vi.fn(),
  })
}

describe('ReportsContent', () => {
  it('names the bounded deadline instead of leaving a silent loading state', () => {
    useObservabilityReport.mockReturnValue({
      data: undefined,
      error: new ObservabilityReportTimeoutError(120_000),
      isLoading: false,
      isError: true,
      isFetching: false,
      refetch: vi.fn(),
    })

    render(<ReportsContent />)

    expect(screen.getByText('Report could not be built')).toBeInTheDocument()
    expect(screen.getByText('Observability report stopped after 120 seconds without a response.')).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'Retry report' })).toBeInTheDocument()
  })

  it('shows real metrics, the named coverage gap, attention, and source trust', () => {
    setSuccessfulQueries()
    render(<ReportsContent />)

    expect(screen.getAllByText('Recorded events').length).toBeGreaterThan(0)
    expect(screen.getAllByText('42').length).toBeGreaterThan(0)
    expect(screen.getAllByText('Controller history starts after this report begins.').length).toBeGreaterThan(0)
    expect(screen.getByText('Deploy failed')).toBeInTheDocument()
    expect(screen.getAllByText('Collector actions').length).toBeGreaterThan(0)
    expect(screen.getByText('80')).toBeInTheDocument()
  })

  it('opens bounded evidence from an attention row', async () => {
    setSuccessfulQueries()
    render(<ReportsContent />)
    fireEvent.click(screen.getByRole('button', { name: 'Inspect' }))

    expect(await screen.findByRole('dialog')).toBeInTheDocument()
    expect(screen.getByText('Recorded evidence')).toBeInTheDocument()
    expect(screen.getAllByText('Deploy failed').length).toBeGreaterThan(1)
    await waitFor(() => expect(useActivitySourceEntries).toHaveBeenLastCalledWith(expect.objectContaining({ sourceId: 'actions', recordId: 'actions:1' }), true, false))
  })

  it('shows execution totals and the linked delivery story', async () => {
    setSuccessfulQueries()
    render(<ReportsContent />)

    expect(screen.getAllByText('Requests in window').length).toBeGreaterThan(0)
    expect(screen.getAllByText('Build observability reports').length).toBeGreaterThan(0)
    expect(screen.getAllByText('observability-reports').length).toBeGreaterThan(0)
    fireEvent.click(screen.getAllByRole('button', { name: 'View story' })[0])

    expect(await screen.findByRole('dialog')).toBeInTheDocument()
    expect(screen.getByText('Delivery story')).toBeInTheDocument()
    expect(screen.getByText('Recorded work')).toBeInTheDocument()
    expect(screen.getByRole('link', { name: 'Open factory run' })).toHaveAttribute('href', '/factory/adw-report')
    expect(screen.getByRole('link', { name: 'Open owner proof' })).toHaveAttribute('href', '/reports')
  })

  it('shows incident recovery, unresolved work, and recurring failures', () => {
    setSuccessfulQueries()
    render(<ReportsContent />)

    expect(screen.getAllByText('Unresolved incidents').length).toBeGreaterThan(0)
    expect(screen.getByText('Buildbox unavailable')).toBeInTheDocument()
    expect(screen.getAllByText('1.0 hr').length).toBeGreaterThan(0)
    expect(screen.getByText('Agent reaper failure')).toBeInTheDocument()
    expect(screen.getAllByText('debian1').length).toBeGreaterThan(0)
  })

  it('shows recorded capacity, account windows, and unavailable efficiency honestly', () => {
    setSuccessfulQueries()
    render(<ReportsContent />)

    expect(screen.getByText('Capacity, accounts, and spend')).toBeInTheDocument()
    expect(screen.getAllByText('Recorded tokens').length).toBeGreaterThan(0)
    expect(screen.getAllByText('120,000').length).toBeGreaterThan(0)
    expect(screen.getByText('Claude · Primary')).toBeInTheDocument()
    expect(screen.getAllByText('63%').length).toBeGreaterThan(0)
    expect(screen.getByText('95th percentile build wait')).toBeInTheDocument()
    expect(screen.getByText('2 min')).toBeInTheDocument()
    expect(screen.getAllByText('Unavailable').length).toBeGreaterThan(0)
    expect(screen.getByRole('link', { name: 'Open account limits' })).toHaveAttribute('href', '/limits')
  })

  it('shows retained history, honest comparisons, and visible chart gaps', () => {
    setSuccessfulQueries()
    render(<ReportsContent />)

    expect(screen.getByText('History and comparison')).toBeInTheDocument()
    expect(screen.getByText(/Recorded history is available from/)).toBeInTheDocument()
    expect(screen.getByText('+7 from 35 in the preceding period')).toBeInTheDocument()
    expect(screen.getByText('The preceding period has missing retained history.')).toBeInTheDocument()
    expect(screen.getAllByText('No retained measurement was recorded for this hour.').length).toBeGreaterThan(0)
    const history = screen.getByRole('heading', { name: 'History and comparison' }).closest('section')
    expect(history?.querySelectorAll('[data-series-segment]')).toHaveLength(2)
  })

  it('shows explicit delivery lineage and names missing evidence', () => {
    setSuccessfulQueries()
    render(<ReportsContent />)

    expect(screen.getByRole('heading', { name: 'Delivery lineage' })).toBeInTheDocument()
    expect(screen.getByText('Each path uses only explicitly linked request, run, check, change, landing, deployment, and owner-proof records.')).toBeInTheDocument()
    expect(screen.getByText('Complete path')).toBeInTheDocument()
    expect(screen.getByText('Incomplete path')).toBeInTheDocument()
    expect(screen.getByText('Owner proof missing')).toBeInTheDocument()
    expect(screen.getAllByText('Ship an older report: No owner-proof link is recorded for this request.').length).toBeGreaterThan(0)
    expect(screen.getByRole('link', { name: 'Open run record' })).toHaveAttribute('href', '/factory/adw-report')
    expect(screen.getByRole('link', { name: 'Open proof record' })).toHaveAttribute('href', '/reports')
  })

  it('saves and reapplies validated owner-local report views', async () => {
    setSuccessfulQueries()
    window.history.replaceState(null, '', '/reports?range=7d&severity=error')
    render(<ReportsContent />)

    const name = screen.getByRole('textbox', { name: 'View name' })
    fireEvent.change(name, { target: { value: 'Weekly failures' } })
    fireEvent.click(screen.getByRole('button', { name: 'Save view' }))
    expect(screen.getByText('Weekly failures')).toBeInTheDocument()
    expect(window.localStorage.getItem('overdeck:observability-report-views:v1')).toContain('?range=7d&severity=error')

    fireEvent.change(screen.getByRole('textbox', { name: 'Search recorded activity' }), { target: { value: 'temporary' } })
    expect(window.location.search).toContain('q=temporary')
    fireEvent.click(screen.getByRole('button', { name: 'Apply' }))
    await waitFor(() => expect(window.location.search).toBe('?range=7d&severity=error'))
  })

  it('offers selected-table CSV, exact JSON, and concise print actions', () => {
    setSuccessfulQueries()
    const print = vi.spyOn(window, 'print').mockImplementation(() => undefined)
    render(<ReportsContent />)

    expect(screen.getByText('CSV includes the selected visible table plus query and coverage metadata. JSON preserves the exact report envelope.')).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'Download CSV' })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'Download JSON' })).toBeInTheDocument()
    fireEvent.click(screen.getByRole('button', { name: 'Print report' }))
    expect(print).toHaveBeenCalledOnce()
    expect(screen.getByLabelText('Printable observability report')).toBeInTheDocument()
  })

  it('keeps filters in the URL and forwards them to the report query', async () => {
    setSuccessfulQueries()
    window.history.replaceState(null, '', '/reports?range=7d&severity=warn&q=deploy')
    render(<ReportsContent />)

    await waitFor(() => expect(useObservabilityReport).toHaveBeenCalledWith(expect.objectContaining({ severity: 'warn', q: 'deploy' }), 7 * 24 * 60 * 60 * 1000, true))
    expect(screen.getByRole('textbox', { name: 'Search recorded activity' })).toHaveValue('deploy')
  })
})
