import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'

import { useAwardsStore } from '../../stores/awards'
import { useAuthStore } from '../../stores/auth'
import type { AdminAuditEntry, AdminAuditQueryOptions } from '../../types/awards'

const auditPageLimit = 80
const allFilter = 'all'

const actionLabels: Record<string, string> = {
  'candidate.create': 'Kandidat angelegt',
  'candidate.delete': 'Kandidat gelöscht',
  'candidate.update': 'Kandidat aktualisiert',
  'category.create': 'Kategorie angelegt',
  'category.delete': 'Kategorie gelöscht',
  'category.update': 'Kategorie aktualisiert',
  'clip.delete': 'Clip gelöscht',
  'clip.status.update': 'Clip-Status geändert',
  'nomination.approve': 'Nominierung übernommen',
  'nomination.reject': 'Nominierung verworfen',
  'operational-settings.update': 'Betriebseinstellungen geändert',
  'result.delete': 'Winner entfernt',
  'result.set': 'Winner gesetzt',
  'risk.resolve': 'Risk Flag entschieden',
  'season.create': 'Jahr angelegt',
  'season.delete': 'Jahr gelöscht',
  'season.phase.update': 'Phase geändert',
  'season.public.update': 'Public-Kontext geändert',
  'season.update': 'Jahr aktualisiert',
  'seed.initialize': 'Seed-Daten initialisiert',
  'site-settings.update': 'Seiteneinstellungen geändert',
}

const groupLabels: Record<string, string> = {
  candidate: 'Kandidaten',
  category: 'Kategorien',
  clip: 'Clips',
  nomination: 'Reviews',
  result: 'Gewinner',
  risk: 'Risiko',
  seed: 'System',
  season: 'Jahre',
  site: 'Seite',
  'site-settings': 'Seite',
  'operational-settings': 'Betrieb',
}

const relatedRoutes: Record<string, { label: string; to: string }> = {
  candidate: { label: 'Kandidaten öffnen', to: '/admin/candidates' },
  category: { label: 'Kategorien öffnen', to: '/admin/categories' },
  clip: { label: 'Clips öffnen', to: '/admin/clips' },
  nomination: { label: 'Review-Fokus öffnen', to: '/admin/nominations?review=1' },
  result: { label: 'Gewinner öffnen', to: '/admin/winners' },
  'risk-flag': { label: 'Risiko öffnen', to: '/admin/risk' },
  season: { label: 'Jahre öffnen', to: '/admin/years' },
  'site-settings': { label: 'Landingpage öffnen', to: '/admin/content' },
  'operational-settings': { label: 'Einstellungen öffnen', to: '/admin/settings' },
}

export interface AuditStat {
  label: string
  value: string
  note: string
}

export interface AuditCountItem {
  key: string
  label: string
  count: number
}

export interface AuditFocusCard {
  label: string
  value: string
  note: string
}

export interface AuditMetadataItem {
  key: string
  value: string
}

export interface AuditChangeItem {
  field: string
  label: string
  from: string
  to: string
  sensitive: boolean
}

export interface AuditRelatedLink {
  label: string
  to: string
}

export interface AuditFilterPreset {
  key: string
  label: string
  description: string
  filters: {
    query?: string
    action?: string
    entityType?: string
  }
}

export interface AuditEntityOption {
  value: string
  label: string
}

export interface AuditLogRow extends AdminAuditEntry {
  actionLabel: string
  actionGroup: string
  actionToneClass: string
  dotClass: string
  entityLabel: string
  createdLabel: string
  ageLabel: string
  metadataItems: AuditMetadataItem[]
  changeItems: AuditChangeItem[]
  requestContextItems: AuditMetadataItem[]
  relatedLink: AuditRelatedLink | null
  rawMetadataJson: string
}

const entityOptions: AuditEntityOption[] = [
  { value: allFilter, label: 'Alle Objekte' },
  { value: 'season', label: 'Jahre' },
  { value: 'category', label: 'Kategorien' },
  { value: 'candidate', label: 'Kandidaten' },
  { value: 'nomination', label: 'Reviews' },
  { value: 'clip', label: 'Clips' },
  { value: 'risk-flag', label: 'Risiko' },
  { value: 'result', label: 'Gewinner' },
  { value: 'site-settings', label: 'Landingpage' },
  { value: 'operational-settings', label: 'Betrieb' },
  { value: 'seed', label: 'System' },
]

const filterPresets: AuditFilterPreset[] = [
  {
    key: 'risk-security',
    label: 'Risk & Security',
    description: 'Entscheidungen mit Sicherheits- oder Missbrauchskontext.',
    filters: { action: 'risk.resolve' },
  },
  {
    key: 'public-context',
    label: 'Public-Kontext',
    description: 'Jahreswechsel, Phasen und sichtbare Public-Änderungen.',
    filters: { entityType: 'season' },
  },
  {
    key: 'content-privacy',
    label: 'Content & Privacy',
    description: 'Landingpage, Datenschutz und Content-Konfiguration.',
    filters: { action: 'site-settings.update' },
  },
  {
    key: 'destructive',
    label: 'Löschungen',
    description: 'Entfernende Aktionen aus Kategorien, Kandidaten, Clips und Jahren.',
    filters: { query: 'delete' },
  },
]

function countBy<T>(items: T[], getKey: (item: T) => string) {
  const counts = new Map<string, number>()
  for (const item of items) {
    const key = getKey(item).trim() || 'unbekannt'
    counts.set(key, (counts.get(key) ?? 0) + 1)
  }

  return [...counts.entries()]
    .map(([key, count]) => ({ key, label: key, count }))
    .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label))
}

function humanizeAction(action: string) {
  if (actionLabels[action]) return actionLabels[action]
  return action
    .split('.')
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1).replaceAll('_', ' '))
    .join(' ')
}

function getActionGroup(action: string) {
  const group = action.split('.')[0] ?? ''
  return groupLabels[group] ?? 'System'
}

function getActionToneClass(action: string) {
  if (action.startsWith('risk')) return 'border-rose-100 bg-rose-50 text-rose-700'
  if (action.startsWith('clip') || action.startsWith('nomination')) return 'border-amber-100 bg-amber-50 text-amber-700'
  if (action.startsWith('result')) return 'border-emerald-100 bg-emerald-50 text-emerald-700'
  if (action.startsWith('season')) return 'border-sky-100 bg-sky-50 text-sky-700'
  if (action.startsWith('site') || action.startsWith('operational')) return 'border-indigo-100 bg-indigo-50 text-indigo-700'
  if (action.includes('delete')) return 'border-slate-200 bg-slate-100 text-slate-700'
  return 'border-cyan-100 bg-cyan-50 text-cyan-700'
}

function getDotClass(action: string) {
  if (action.startsWith('risk')) return 'bg-rose-400'
  if (action.startsWith('clip') || action.startsWith('nomination')) return 'bg-amber-400'
  if (action.startsWith('result')) return 'bg-emerald-400'
  if (action.startsWith('season')) return 'bg-sky-400'
  if (action.startsWith('site') || action.startsWith('operational')) return 'bg-indigo-400'
  if (action.includes('delete')) return 'bg-slate-400'
  return 'bg-cyan-400'
}

function stringifyMetadataValue(entryValue: unknown) {
  if (typeof entryValue === 'object') return JSON.stringify(entryValue)
  return String(entryValue)
}

function parseMetadata(metadataJson: string | undefined) {
  if (!metadataJson) return []

  try {
    const value = JSON.parse(metadataJson) as Record<string, unknown>
    if (!value || typeof value !== 'object' || Array.isArray(value)) return []

    return Object.entries(value)
      .filter(([key]) => key !== 'changes' && key !== 'Changes')
      .filter(([, entryValue]) => entryValue !== null && entryValue !== undefined && entryValue !== '')
      .map(([key, entryValue]) => ({
        key,
        value: stringifyMetadataValue(entryValue),
      }))
  } catch {
    return [{ key: 'metadata', value: metadataJson }]
  }
}

function parseChangeItems(metadataJson: string | undefined): AuditChangeItem[] {
  if (!metadataJson) return []

  try {
    const value = JSON.parse(metadataJson) as Record<string, unknown>
    const changes = value.changes ?? value.Changes
    if (!Array.isArray(changes)) return []

    return changes
      .map((change) => {
        if (!change || typeof change !== 'object' || Array.isArray(change)) return null

        const item = change as Record<string, unknown>
        return {
          field: String(item.field ?? item.Field ?? ''),
          label: String(item.label ?? item.Label ?? item.field ?? item.Field ?? 'Änderung'),
          from: String(item.from ?? item.From ?? ''),
          to: String(item.to ?? item.To ?? ''),
          sensitive: Boolean(item.sensitive ?? item.Sensitive),
        }
      })
      .filter((change): change is AuditChangeItem => Boolean(change?.field || change?.label))
  } catch {
    return []
  }
}

function formatDate(value: string) {
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) return value
  return date.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' })
}

function formatAge(value: string) {
  const timestamp = new Date(value).getTime()
  if (Number.isNaN(timestamp)) return 'Zeitpunkt unbekannt'

  const minutes = Math.max(0, Math.round((Date.now() - timestamp) / 60_000))
  if (minutes < 2) return 'gerade eben'
  if (minutes < 60) return `vor ${minutes} Min.`

  const hours = Math.round(minutes / 60)
  if (hours < 48) return `vor ${hours} Std.`

  return `vor ${Math.round(hours / 24)} Tagen`
}

function escapeCsvCell(value: unknown) {
  const text = String(value ?? '')
  if (!/[",\n\r;]/.test(text)) return text
  return `"${text.replaceAll('"', '""')}"`
}

function getDateBoundary(value: string, isEndOfDay: boolean) {
  if (!value) return undefined
  const suffix = isEndOfDay ? 'T23:59:59.999' : 'T00:00:00.000'
  const date = new Date(`${value}${suffix}`)
  return Number.isNaN(date.getTime()) ? undefined : date.toISOString()
}

function buildRequestContextItems(entry: AdminAuditEntry, canViewAuditIp: boolean) {
  return [
    ...(canViewAuditIp ? [{ key: 'IP', value: entry.createdFromIp || 'nicht erfasst' }] : []),
    { key: 'User-Agent', value: entry.userAgent || 'nicht erfasst' },
  ]
}

function buildRelatedLink(entry: AdminAuditEntry) {
  const route = relatedRoutes[entry.entityType]
  if (!route) return null

  if (entry.entityType === 'nomination' && entry.entityId) {
    return { label: route.label, to: `${route.to}?nominationId=${encodeURIComponent(entry.entityId)}` }
  }

  return route
}

function createAuditRow(entry: AdminAuditEntry, canViewAuditIp: boolean): AuditLogRow {
  return {
    ...entry,
    actionLabel: humanizeAction(entry.actionType),
    actionGroup: getActionGroup(entry.actionType),
    actionToneClass: getActionToneClass(entry.actionType),
    dotClass: getDotClass(entry.actionType),
    entityLabel: `${entry.entityType} ${entry.entityId}`.trim(),
    createdLabel: formatDate(entry.createdAt),
    ageLabel: formatAge(entry.createdAt),
    metadataItems: parseMetadata(entry.metadataJson),
    changeItems: parseChangeItems(entry.metadataJson),
    requestContextItems: buildRequestContextItems(entry, canViewAuditIp),
    relatedLink: buildRelatedLink(entry),
    rawMetadataJson: entry.metadataJson || '{}',
  }
}

function downloadCsv(entries: AdminAuditEntry[], canViewAuditIp: boolean) {
  const rows = [
    [
      'Id',
      'Admin',
      'Aktion',
      'Objekt-Typ',
      'Objekt-Id',
      'Zusammenfassung',
      'Zeitpunkt',
      ...(canViewAuditIp ? ['IP'] : []),
      'User-Agent',
      'Metadaten',
    ],
    ...entries.map((entry) => [
      entry.id,
      entry.adminTwitchUserId,
      entry.actionType,
      entry.entityType,
      entry.entityId,
      entry.summary,
      new Date(entry.createdAt).toISOString(),
      ...(canViewAuditIp ? [entry.createdFromIp ?? ''] : []),
      entry.userAgent,
      entry.metadataJson,
    ]),
  ]

  const csv = rows.map((row) => row.map(escapeCsvCell).join(';')).join('\n')
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = `vtuber-star-awards-audit-${new Date().toISOString().slice(0, 10)}.csv`
  document.body.append(link)
  link.click()
  link.remove()
  URL.revokeObjectURL(url)
}

export function useAdminAuditManager() {
  const store = useAwardsStore()
  const authStore = useAuthStore()
  const query = ref('')
  const selectedAdmin = ref(allFilter)
  const selectedAction = ref(allFilter)
  const entityFilter = ref(allFilter)
  const fromDate = ref('')
  const toDate = ref('')
  const loadingAudit = ref(false)
  const loadingMore = ref(false)
  const auditError = ref('')
  const exportMessage = ref('')
  const lastLoadedAt = ref<Date | null>(null)
  const auditEntries = ref<AdminAuditEntry[]>([])
  const totalCount = ref(0)
  const nextCursor = ref<string | null>(null)
  const selectedEntry = ref<AuditLogRow | null>(null)
  const appliedPresetKey = ref('')
  let searchTimer: ReturnType<typeof window.setTimeout> | null = null
  let exportMessageTimer: ReturnType<typeof window.setTimeout> | null = null
  let suppressFilterWatch = false

  const adminCounts = computed(() => countBy(auditEntries.value, (entry) => entry.adminTwitchUserId))
  const actionCounts = computed(() =>
    countBy(auditEntries.value, (entry) => entry.actionType).map((item) => ({
      ...item,
      label: humanizeAction(item.key),
    })),
  )
  const entityCounts = computed(() =>
    countBy(auditEntries.value, (entry) => entry.entityType).map((item) => ({
      ...item,
      label: entityOptions.find((option) => option.value === item.key)?.label ?? item.key,
    })),
  )
  const canViewAuditIp = computed(() => authStore.isOwnerOrCreator)
  const auditRows = computed<AuditLogRow[]>(() => auditEntries.value.map((entry) => createAuditRow(entry, canViewAuditIp.value)))
  const metadataCount = computed(() => auditEntries.value.filter((entry) => parseMetadata(entry.metadataJson).length > 0).length)
  const requestContextCount = computed(() => auditEntries.value.filter((entry) => entry.userAgent || (canViewAuditIp.value && entry.createdFromIp)).length)
  const recentDayCount = computed(() => {
    const minTimestamp = Date.now() - 24 * 60 * 60 * 1000
    return auditEntries.value.filter((entry) => new Date(entry.createdAt).getTime() >= minTimestamp).length
  })
  const hasMore = computed(() => Boolean(nextCursor.value))
  const loadedCountLabel = computed(() => auditEntries.value.length.toLocaleString('de-DE'))
  const totalCountLabel = computed(() => totalCount.value.toLocaleString('de-DE'))
  const logStats = computed<AuditStat[]>(() => [
    { label: 'Geladen', value: loadedCountLabel.value, note: `Page ${auditPageLimit}` },
    { label: 'Treffer', value: totalCountLabel.value, note: 'serverseitig gefiltert' },
    { label: '24h', value: recentDayCount.value.toLocaleString('de-DE'), note: 'neue Aktionen' },
    { label: 'Kontext', value: requestContextCount.value.toLocaleString('de-DE'), note: canViewAuditIp.value ? 'mit IP oder User-Agent' : 'mit User-Agent' },
  ])
  const focusCards = computed<AuditFocusCard[]>(() => {
    const topAdmin = adminCounts.value[0]
    const topAction = actionCounts.value[0]
    const topEntity = entityCounts.value[0]

    return [
      {
        label: 'Top Admin',
        value: topAdmin?.label ?? 'Keine Daten',
        note: topAdmin ? `${topAdmin.count} Aktionen in den geladenen Treffern` : 'Noch kein Admin aktiv',
      },
      {
        label: 'Top Aktion',
        value: topAction?.label ?? 'Keine Daten',
        note: topAction ? `${topAction.count} Einträge` : 'Noch kein Aktionstyp vorhanden',
      },
      {
        label: 'Objekt-Fokus',
        value: topEntity?.label ?? 'Keine Daten',
        note: topEntity ? `${topEntity.count} Einträge` : 'Noch kein Objekttyp vorhanden',
      },
      {
        label: 'Details',
        value: metadataCount.value.toLocaleString('de-DE'),
        note: 'Einträge mit sichtbaren Metadaten',
      },
    ]
  })
  const activeFilterCount = computed(() =>
    (query.value.trim() ? 1 : 0) +
    (selectedAdmin.value !== allFilter ? 1 : 0) +
    (selectedAction.value !== allFilter ? 1 : 0) +
    (entityFilter.value !== allFilter ? 1 : 0) +
    (fromDate.value ? 1 : 0) +
    (toDate.value ? 1 : 0),
  )
  const lastLoadedLabel = computed(() =>
    lastLoadedAt.value ? lastLoadedAt.value.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) : 'Noch nicht aktualisiert',
  )
  const emptyStateText = computed(() =>
    activeFilterCount.value > 0
      ? 'Keine Log-Einträge passen zu den aktiven Filtern.'
      : 'Noch keine Audit-Einträge vorhanden.',
  )
  const pageSummaryLabel = computed(() =>
    `${loadedCountLabel.value} von ${totalCountLabel.value} Treffern geladen`,
  )

  function buildAuditOptions(cursor: string | null = null): AdminAuditQueryOptions {
    return {
      limit: auditPageLimit,
      query: query.value,
      admin: selectedAdmin.value === allFilter ? undefined : selectedAdmin.value,
      action: selectedAction.value === allFilter ? undefined : selectedAction.value,
      entityType: entityFilter.value === allFilter ? undefined : entityFilter.value,
      from: getDateBoundary(fromDate.value, false),
      to: getDateBoundary(toDate.value, true),
      cursor,
    }
  }

  function clearTimers() {
    if (searchTimer) {
      window.clearTimeout(searchTimer)
      searchTimer = null
    }
  }

  function scheduleLoad() {
    clearTimers()
    searchTimer = window.setTimeout(() => {
      void loadAuditEntries()
    }, 350)
  }

  async function loadAuditEntries(options: { append?: boolean } = {}) {
    const append = options.append ?? false
    if (append && !nextCursor.value) return

    if (append) {
      loadingMore.value = true
    } else {
      loadingAudit.value = true
      selectedEntry.value = null
    }

    auditError.value = ''

    try {
      const response = await store.loadAdminAuditEntriesPage(
        buildAuditOptions(append ? nextCursor.value : null),
        append,
      )
      auditEntries.value = append ? [...auditEntries.value, ...response.items] : response.items
      totalCount.value = response.totalCount
      nextCursor.value = response.nextCursor
      lastLoadedAt.value = new Date()
    } catch (error) {
      auditError.value = error instanceof Error ? error.message : 'Audit-Logs konnten nicht geladen werden.'
    } finally {
      loadingAudit.value = false
      loadingMore.value = false
    }
  }

  async function loadNextPage() {
    await loadAuditEntries({ append: true })
  }

  function exportAuditCsv() {
    if (auditEntries.value.length === 0) return
    downloadCsv(auditEntries.value, canViewAuditIp.value)
    exportMessage.value = `CSV mit ${auditEntries.value.length.toLocaleString('de-DE')} geladenen Einträgen erstellt.`
    if (exportMessageTimer) window.clearTimeout(exportMessageTimer)
    exportMessageTimer = window.setTimeout(() => {
      exportMessage.value = ''
    }, 3500)
  }

  async function clearFilters() {
    clearTimers()
    suppressFilterWatch = true
    query.value = ''
    selectedAdmin.value = allFilter
    selectedAction.value = allFilter
    entityFilter.value = allFilter
    fromDate.value = ''
    toDate.value = ''
    appliedPresetKey.value = ''
    suppressFilterWatch = false
    await loadAuditEntries()
  }

  async function applyPreset(presetKey: string) {
    const preset = filterPresets.find((item) => item.key === presetKey)
    if (!preset) return

    clearTimers()
    suppressFilterWatch = true
    query.value = preset.filters.query ?? ''
    selectedAdmin.value = allFilter
    selectedAction.value = preset.filters.action ?? allFilter
    entityFilter.value = preset.filters.entityType ?? allFilter
    fromDate.value = ''
    toDate.value = ''
    appliedPresetKey.value = preset.key
    suppressFilterWatch = false
    await loadAuditEntries()
  }

  function openAuditEntry(entry: AuditLogRow) {
    selectedEntry.value = entry
  }

  function closeAuditEntry() {
    selectedEntry.value = null
  }

  watch([query, selectedAdmin, selectedAction, entityFilter, fromDate, toDate], () => {
    if (suppressFilterWatch) return
    appliedPresetKey.value = ''
    scheduleLoad()
  })

  onMounted(() => {
    auditEntries.value = store.admin.auditEntries
    totalCount.value = store.admin.auditEntries.length
    void loadAuditEntries()
  })

  onBeforeUnmount(() => {
    clearTimers()
    if (exportMessageTimer) window.clearTimeout(exportMessageTimer)
  })

  return {
    query,
    selectedAdmin,
    selectedAction,
    entityFilter,
    fromDate,
    toDate,
    loadingAudit,
    loadingMore,
    auditError,
    exportMessage,
    selectedEntry,
    auditRows,
    adminCounts,
    actionCounts,
    entityCounts,
    logStats,
    focusCards,
    activeFilterCount,
    lastLoadedLabel,
    emptyStateText,
    pageSummaryLabel,
    hasMore,
    filterPresets,
    appliedPresetKey,
    entityOptions,
    loadAuditEntries,
    loadNextPage,
    exportAuditCsv,
    clearFilters,
    applyPreset,
    openAuditEntry,
    closeAuditEntry,
  }
}
