'use server'

// QAF-Vergleich-Export server actions (Demo-067 C/E, KAR-982/KAR-984) —
// sibling to app/qaf-differences/actions.ts (that file is off-limits for
// this task: a 4750-line file with its own review discipline). Same
// rehydrate discipline as that file's exportQafComparisonXlsx: no re-parse
// of the original xlsx, re-run the deterministic engine from persisted
// qaf_* rows via lib/qaf-differences's own rehydrateExportInput, so the
// DOCX/Markdown narrative matches the on-screen/8-sheet-XLSX result
// exactly. RLS-scoped read, no service-role client.
//
// Two format-specific actions (not one action with a format parameter) —
// same shape app/project/[id]/export/actions.ts already uses for its
// DOCX/XLSX pair, and the only shape that works here: a Server Component
// passes these directly as CopilotExportButton props, so each format needs
// its own top-level 'use server' export (an inline wrapper closure could
// not cross the Server->Client boundary).
//
// Deliberately does NOT write a `qaf_export` audit row — that table's
// `format` column is `'xlsx' | 'pdf'`-shaped for the existing exports
// (schema change to add a new format is out of this task's scope; see task
// "Keine Schema-/Migrations-Änderungen").

import { z } from 'zod'
import type { SupabaseClient } from '@supabase/supabase-js'
import { createClient } from '@/lib/supabase/server'
import { getProfile } from '@/config/profiles'
import {
  buildQafComparisonCopilotDocx,
  buildQafComparisonCopilotMd,
  copilotExportsGate,
  sanitizeFilenamePart,
  type CopilotExportDownload,
  type QafComparisonDocxInput,
} from '@/lib/copilot-export'
import {
  comparisonModeRule,
  decodeBilingual,
  rehydrateExportInput,
  type PlausibilityIssue,
  type PlausibilitySeverity,
  type RehydratedFile,
} from '@/lib/qaf-differences'
import type { QAFRow } from '@/lib/qaf-parser'

export type ActionResult<T = unknown> = { ok: true; data: T } | { ok: false; error: string }

const uuidSchema = z
  .string()
  .regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, 'invalid uuid')

/** Shared data load for both format actions below — same registry-driven
 * mode guard, RLS reads, and rehydrate call either format's builder needs,
 * pulled out once so the DOCX and Markdown actions don't duplicate them.
 * Returns `partNumberForFilename` alongside the builder input rather than
 * folding it into `QafComparisonDocxInput`: the filename must stay keyed on
 * the persisted `qaf_comparison.part_number` column (what the user/import
 * set), not `QafComparisonResult.partNumber` (the re-parsed summary value,
 * `neu.summary.partNumber.value ?? alt.summary.partNumber.value` per
 * lib/qaf-differences/internal/compare.ts — can legitimately differ from
 * the stored column). */
async function loadQafComparisonExportInput(
  supabase: SupabaseClient,
  comparisonId: string,
  generatedAtLabel: string,
): Promise<
  | { ok: true; data: { input: QafComparisonDocxInput; partNumberForFilename: string | null } }
  | { ok: false; error: string }
> {
  const { data: cmp, error: cmpErr } = await supabase
    .from('qaf_comparison')
    .select('id, project_id, part_number, baseline_file_id, comparison_file_id, comparison_mode, engine_version')
    .eq('id', comparisonId)
    .maybeSingle()
  if (cmpErr) return { ok: false, error: `comparison load failed: ${cmpErr.message}` }
  if (!cmp) return { ok: false, error: 'Vergleich nicht gefunden oder kein Zugriff.' }

  // Same registry-driven guard as exportQafComparisonXlsx — G60/Multi-QAF/
  // Variant-vs-Standard comparisons have no qaf_manufacturing_step rows
  // (their own dedicated exports cover them), so this would otherwise
  // silently produce a misleadingly empty export.
  const exportRule = comparisonModeRule(cmp.comparison_mode as string | null)
  if (!exportRule.exportSupported) {
    return {
      ok: false,
      error: exportRule.exportNotSupportedError ?? 'Copilot-Export für diesen Vergleichs-Modus nicht verfügbar.',
    }
  }
  if (!cmp.baseline_file_id || !cmp.comparison_file_id) {
    return { ok: false, error: 'Vergleich hat keine zwei Dateien — Export nicht möglich.' }
  }

  const fileIds = [cmp.baseline_file_id, cmp.comparison_file_id] as string[]

  const [projectRes, filesRes, partRes, stepsRes, plausRes] = await Promise.all([
    supabase.from('projects').select('is_demo').eq('id', cmp.project_id).maybeSingle(),
    supabase.from('qaf_file').select('id, original_file_name, template_type').in('id', fileIds),
    cmp.part_number
      ? supabase
          .from('qaf_part')
          .select('part_number, part_name, variant, project_code, supplier, quotation_date, version')
          .eq('project_id', cmp.project_id)
          .eq('part_number', cmp.part_number as string)
          .maybeSingle()
      : Promise.resolve({ data: null, error: null }),
    supabase.from('qaf_manufacturing_step').select('file_id, raw_values').in('file_id', fileIds),
    supabase
      .from('qaf_plausibility_issue')
      .select('issue_type, severity, step_label, field, explanation')
      .eq('comparison_id', comparisonId),
  ])

  if (projectRes.error) return { ok: false, error: `project load failed: ${projectRes.error.message}` }
  if (filesRes.error) return { ok: false, error: `file load failed: ${filesRes.error.message}` }
  if (partRes.error) return { ok: false, error: `part load failed: ${partRes.error.message}` }
  if (stepsRes.error) return { ok: false, error: `step load failed: ${stepsRes.error.message}` }
  if (plausRes.error) return { ok: false, error: `plausibility load failed: ${plausRes.error.message}` }

  const fileById = new Map((filesRes.data ?? []).map((f) => [f.id, f]))
  const baselineFile = fileById.get(cmp.baseline_file_id)
  const comparisonFile = fileById.get(cmp.comparison_file_id)
  if (!baselineFile || !comparisonFile) return { ok: false, error: 'Dateizeilen fehlen — Export nicht möglich.' }

  const stepsByFile = new Map<string, QAFRow[]>()
  for (const row of stepsRes.data ?? []) {
    const list = stepsByFile.get(row.file_id) ?? []
    list.push(row.raw_values as QAFRow)
    stepsByFile.set(row.file_id, list)
  }

  const part = (partRes.data ?? null) as RehydratedFile['part']
  const baseline: RehydratedFile = { file: baselineFile, part, steps: stepsByFile.get(cmp.baseline_file_id) ?? [] }
  const comparison: RehydratedFile = { file: comparisonFile, part, steps: stepsByFile.get(cmp.comparison_file_id) ?? [] }

  const plausibilityOverride: PlausibilityIssue[] = (plausRes.data ?? []).map((r) => {
    const { de, en } = decodeBilingual(r.explanation)
    return {
      type: r.issue_type,
      severity: r.severity as PlausibilitySeverity,
      field: r.field ?? undefined,
      step: r.step_label ?? undefined,
      explanation: de,
      explanationEn: en || undefined,
    }
  })

  let exportInput: ReturnType<typeof rehydrateExportInput>
  try {
    exportInput = rehydrateExportInput({
      generatedAtLabel,
      baseline,
      comparison,
      plausibilityOverride,
      engineVersion: cmp.engine_version,
    })
  } catch (e) {
    return { ok: false, error: `Vergleich konnte nicht neu berechnet werden: ${e instanceof Error ? e.message : String(e)}` }
  }
  const comparisonResult = exportInput.comparisons[0]
  if (!comparisonResult) return { ok: false, error: 'Vergleich lieferte kein Ergebnis.' }

  return {
    ok: true,
    data: {
      input: { comparison: comparisonResult, isDemo: projectRes.data?.is_demo === true, generatedAtLabel },
      partNumberForFilename: cmp.part_number,
    },
  }
}

export async function exportQafComparisonCopilotDocx(
  comparisonId: string,
  generatedAtLabel: string,
): Promise<ActionResult<CopilotExportDownload>> {
  if (!uuidSchema.safeParse(comparisonId).success) return { ok: false, error: 'invalid comparison id' }

  const gate = copilotExportsGate(getProfile().features.copilotExports)
  if (!gate.ok) return gate

  const supabase = await createClient()
  const loaded = await loadQafComparisonExportInput(supabase, comparisonId, generatedAtLabel)
  if (!loaded.ok) return loaded

  let buffer: Buffer
  try {
    buffer = await buildQafComparisonCopilotDocx(loaded.data.input)
  } catch (e) {
    return { ok: false, error: `Export-Erzeugung fehlgeschlagen: ${e instanceof Error ? e.message : String(e)}` }
  }

  const base64 = buffer.toString('base64')
  const filename = `QAF_Vergleich_Copilot_${sanitizeFilenamePart(loaded.data.partNumberForFilename, 'QAF')}.docx`
  return { ok: true, data: { filename, base64 } }
}

export async function exportQafComparisonCopilotMd(
  comparisonId: string,
  generatedAtLabel: string,
): Promise<ActionResult<CopilotExportDownload>> {
  if (!uuidSchema.safeParse(comparisonId).success) return { ok: false, error: 'invalid comparison id' }

  const gate = copilotExportsGate(getProfile().features.copilotExports)
  if (!gate.ok) return gate

  const supabase = await createClient()
  const loaded = await loadQafComparisonExportInput(supabase, comparisonId, generatedAtLabel)
  if (!loaded.ok) return loaded

  let md: string
  try {
    md = buildQafComparisonCopilotMd(loaded.data.input)
  } catch (e) {
    return { ok: false, error: `Export-Erzeugung fehlgeschlagen: ${e instanceof Error ? e.message : String(e)}` }
  }

  const base64 = Buffer.from(md, 'utf-8').toString('base64')
  const filename = `QAF_Vergleich_Copilot_${sanitizeFilenamePart(loaded.data.partNumberForFilename, 'QAF')}.md`
  return { ok: true, data: { filename, base64 } }
}
