import type {
  ActivityReportSection,
  CapacityReportSection,
  ExecutionReportSection,
  LineageReportSection,
  ObservabilityReport,
  ReliabilityReportSection,
} from '@overdeck/report-contract'
import { parseReportUrlState, serializeReportUrlState, type ReportUrlState } from './report-url-state'

export const SAVED_REPORT_VIEWS_KEY = 'overdeck:observability-report-views:v1'
export const MAX_SAVED_REPORT_VIEWS = 20

export interface SavedReportView {
  id: string
  name: string
  query: string
}

export interface ReportExportTable {
  id: string
  label: string
}

interface CsvTable {
  headers: string[]
  rows: Array<Array<string | number | null | undefined>>
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function canonicalSavedQuery(value: unknown): string | undefined {
  if (typeof value !== 'string' || value.length > 4_000 || (!value.startsWith('?') && value !== '')) return undefined
  const parsed = parseReportUrlState(value)
  const canonical = serializeReportUrlState(parsed)
  return canonical === value ? canonical : undefined
}

export function parseSavedReportViews(raw: string | null): SavedReportView[] {
  if (!raw) return []
  let value: unknown
  try {
    value = JSON.parse(raw)
  } catch {
    return []
  }
  if (!Array.isArray(value)) return []
  const ids = new Set<string>()
  const views: SavedReportView[] = []
  for (const candidate of value) {
    if (!isRecord(candidate) || typeof candidate.id !== 'string' || !/^[a-zA-Z0-9_-]{1,80}$/.test(candidate.id)) continue
    if (ids.has(candidate.id) || typeof candidate.name !== 'string') continue
    const name = candidate.name.trim()
    const query = canonicalSavedQuery(candidate.query)
    if (!name || name.length > 80 || query === undefined) continue
    ids.add(candidate.id)
    views.push({ id: candidate.id, name, query })
    if (views.length === MAX_SAVED_REPORT_VIEWS) break
  }
  return views
}

export function savedViewFilters(view: SavedReportView): ReportUrlState {
  return parseReportUrlState(view.query)
}

export function createSavedReportView(id: string, name: string, filters: ReportUrlState): SavedReportView | undefined {
  const trimmedName = name.trim()
  if (!/^[a-zA-Z0-9_-]{1,80}$/.test(id) || !trimmedName || trimmedName.length > 80) return undefined
  return { id, name: trimmedName, query: serializeReportUrlState(filters) }
}

function section<T extends ObservabilityReport['sections'][number]['kind']>(report: ObservabilityReport, kind: T) {
  return report.sections.find((candidate) => candidate.kind === kind) as Extract<ObservabilityReport['sections'][number], { kind: T }> | undefined
}

export function reportExportTables(report: ObservabilityReport): ReportExportTable[] {
  const activity = section(report, 'activity')
  const execution = section(report, 'execution')
  const reliability = section(report, 'reliability')
  const capacity = section(report, 'capacity')
  const lineage = section(report, 'lineage')
  return [
    ...(activity ? [{ id: 'attention', label: 'Needs attention' }, { id: 'sources', label: 'Source trust' }] : []),
    ...(execution ? [{ id: 'requests', label: 'Requests' }, { id: 'runs', label: 'Factory runs' }] : []),
    ...(reliability ? [{ id: 'incidents', label: 'Incidents' }, { id: 'failures', label: 'Recorded failures' }] : []),
    ...(capacity ? [{ id: 'accounts', label: 'Account windows' }, { id: 'usage-model', label: 'Usage by model' }, { id: 'usage-account', label: 'Usage by account' }, { id: 'usage-host', label: 'Usage by host' }, { id: 'usage-project', label: 'Usage by project' }] : []),
    ...(lineage ? [{ id: 'lineage', label: 'Delivery lineage' }] : []),
  ]
}

function activityTable(report: ObservabilityReport, id: string): CsvTable | undefined {
  const value = section(report, 'activity') as ActivityReportSection | undefined
  if (!value) return undefined
  if (id === 'attention') return {
    headers: ['Title', 'Severity', 'Source', 'Recorded', 'Project', 'Host', 'Account'],
    rows: value.attention.map((row) => row.kind === 'event'
      ? [row.title, row.severity, row.sourceId, row.ts, row.project, row.host, row.account]
      : [row.title, row.severity, row.sourceId, null, null, null, null]),
  }
  if (id === 'sources') return {
    headers: ['Source', 'Authority', 'Status', 'Matching records', 'Retained records', 'Earliest', 'Latest', 'Coverage reason'],
    rows: value.sources.map((row) => [row.label, row.authority, row.status, row.matchingRecords, row.retainedRecords, row.earliest, row.latest, row.reason]),
  }
  return undefined
}

function executionTable(report: ObservabilityReport, id: string): CsvTable | undefined {
  const value = section(report, 'execution') as ExecutionReportSection | undefined
  if (!value) return undefined
  if (id === 'requests') return {
    headers: ['Request', 'Project', 'State', 'Asked', 'Updated', 'Run linkage', 'Landed', 'Deployed', 'Owner proof'],
    rows: value.requests.map((row) => [row.title, row.project, row.state, row.askedAt, row.updatedAt, row.correlation, row.delivery.landedAt, row.delivery.deployedAt, row.proofHref]),
  }
  if (id === 'runs') return {
    headers: ['Run', 'Project', 'Outcome', 'Started', 'Ended', 'Duration ms', 'Attempts', 'Retries', 'Failed checks', 'Changed files'],
    rows: value.runs.map((row) => [row.label, row.project, row.outcome, row.startedAt, row.endedAt, row.durationMs, row.attempts, row.retries, row.failedGates, row.changedFiles]),
  }
  return undefined
}

function reliabilityTable(report: ObservabilityReport, id: string): CsvTable | undefined {
  const value = section(report, 'reliability') as ReliabilityReportSection | undefined
  if (!value) return undefined
  if (id === 'incidents') return {
    headers: ['Incident', 'State', 'Priority', 'Type', 'Created', 'Resolved', 'Duration ms', 'Age ms', 'Failure class'],
    rows: value.incidents.map((row) => [row.title, row.state, row.priority, row.incidentType, row.createdAt, row.resolvedAt, row.durationMs, row.ageMs, row.failureClass]),
  }
  if (id === 'failures') return {
    headers: ['Failure class', 'Source', 'Category', 'Count', 'First', 'Latest', 'Project', 'Host', 'Service'],
    rows: value.failureGroups.map((row) => [row.label, row.source, row.category, row.count, row.firstAt, row.latestAt, row.project, row.host, row.service]),
  }
  return undefined
}

function usageRows(rows: CapacityReportSection['byModel']): CsvTable {
  return {
    headers: ['Recorded as', 'Tokens', 'Provider cost', 'Attempts', 'Attribution missing'],
    rows: rows.map((row) => [row.label, row.tokens, row.cost, row.attempts, row.unattributed ? 'yes' : 'no']),
  }
}

function capacityTable(report: ObservabilityReport, id: string): CsvTable | undefined {
  const value = section(report, 'capacity') as CapacityReportSection | undefined
  if (!value) return undefined
  if (id === 'accounts') return {
    headers: ['Account', 'Provider', 'Window used percent', 'Status', 'Recorded spend', 'Spend limit', 'Currency', 'Reset'],
    rows: value.accounts.map((row) => [row.label, row.provider, row.usedPercent, row.status, row.spendAmount, row.spendLimit, row.currency, row.resetAt]),
  }
  if (id === 'usage-model') return usageRows(value.byModel)
  if (id === 'usage-account') return usageRows(value.byAccount)
  if (id === 'usage-host') return usageRows(value.byHost)
  if (id === 'usage-project') return usageRows(value.byProject)
  return undefined
}

function lineageTable(report: ObservabilityReport, id: string): CsvTable | undefined {
  if (id !== 'lineage') return undefined
  const value = section(report, 'lineage') as LineageReportSection | undefined
  if (!value) return undefined
  return {
    headers: ['Request', 'Project', 'Complete', 'Stage', 'Status', 'Detail', 'Recorded', 'Evidence source', 'Evidence record'],
    rows: value.lineages.flatMap((lineage) => lineage.stages.map((stage) => [
      lineage.title,
      lineage.project,
      lineage.complete ? 'yes' : 'no',
      stage.label,
      stage.status,
      stage.detail,
      stage.recordedAt,
      stage.evidence?.source,
      stage.evidence?.recordId,
    ])),
  }
}

export function escapeCsvFormula(value: string): string {
  return /^\s*[=+\-@]/.test(value) || /^[\t\r]/.test(value) ? `'${value}` : value
}

function csvCell(value: string | number | null | undefined): string {
  const text = escapeCsvFormula(value === null || value === undefined ? '' : String(value))
  return `"${text.replaceAll('"', '""')}"`
}

export function buildReportCsv(report: ObservabilityReport, tableId: string): string | undefined {
  const table = activityTable(report, tableId)
    ?? executionTable(report, tableId)
    ?? reliabilityTable(report, tableId)
    ?? capacityTable(report, tableId)
    ?? lineageTable(report, tableId)
  if (!table) return undefined
  const coverageMetadata: Array<[string, string]> = [
    ...report.coverage.sources.map((source, index) => [
      `Coverage source ${index + 1}`,
      `${source.label} | ${source.authority} | ${source.status}${source.reason ? ` | ${source.reason}` : ''}`,
    ] as [string, string]),
    ...report.coverage.gaps.map((gap, index) => [
      `Coverage gap ${index + 1}`,
      `${gap.domain}${gap.sourceId ? ` | ${gap.sourceId}` : ''}${gap.metric ? ` | ${gap.metric}` : ''}${gap.from || gap.to ? ` | ${gap.from ?? 'unknown'} to ${gap.to ?? 'unknown'}` : ''} | ${gap.reason}`,
    ] as [string, string]),
  ]
  const metadata: Array<[string, string | number]> = [
    ['Schema version', report.schemaVersion],
    ['Generated at', report.generatedAt],
    ['Coverage', report.coverage.status],
    ['Coverage gaps', report.coverage.gaps.length],
    ['From', report.query.from],
    ['To', report.query.to],
    ['Timezone', report.query.timezone],
    ['Projects', (report.query.projects ?? []).join(' | ')],
    ['Sources', (report.query.sources ?? []).join(' | ')],
    ['Minimum severity', report.query.severity ?? 'all'],
    ['Search', report.query.q ?? ''],
    ...coverageMetadata,
  ]
  return [
    ['Report metadata', 'Value'].map(csvCell).join(','),
    ...metadata.map((row) => row.map(csvCell).join(',')),
    '',
    table.headers.map(csvCell).join(','),
    ...table.rows.map((row) => row.map(csvCell).join(',')),
  ].join('\r\n')
}

export function buildReportJson(report: ObservabilityReport): string {
  return JSON.stringify(report, null, 2)
}
