import { calculateReportScore, getEntryTime } from "./scoring";
import { attachIntegrity, createExportIntegrity, summarizeVerification, verifyEntryIntegrity } from "./integrity";
import { demoEntries } from "./demoData";
import type { DelayedFollowup, ExportPayload, FollowupDraft, ImportSummary, Report, ReportDraft, StoredEntry } from "./types";

const STORAGE_KEY = "arbeidsevne-egenvurdering:entries";
const DEMO_STORAGE_KEY = "arbeidspuls:demo-entries";
const DEMO_MODE_KEY = "arbeidspuls:demo-mode";
const DEMO_SEEDED_KEY = "arbeidspuls:demo-seeded";
const MAX_IMPORT_BYTES = 5 * 1024 * 1024;
const BLOCKED_IMPORT_KEYS = new Set(["__proto__", "constructor", "prototype"]);

export type DataMode = "normal" | "demo";
export type ExportRange = {
  fromDate?: string;
  toDate?: string;
};

function storageKey(mode: DataMode) {
  return mode === "demo" ? DEMO_STORAGE_KEY : STORAGE_KEY;
}

function modeFromDemoFlag(demoMode = false): DataMode {
  return demoMode ? "demo" : "normal";
}

export function isDemoModeEnabled() {
  return localStorage.getItem(DEMO_MODE_KEY) === "true";
}

export function setDemoModeEnabled(enabled: boolean) {
  localStorage.setItem(DEMO_MODE_KEY, enabled ? "true" : "false");
  if (enabled) ensureDemoData();
}

export function ensureDemoData() {
  const existing = readEntries("demo");
  if (existing.length > 0) return existing;
  if (localStorage.getItem(DEMO_SEEDED_KEY) === "true") return [];
  const seeded = demoEntries.map((entry) => markDemoEntry(normalizeStoredEntry(entry)));
  writeEntries(seeded, "demo");
  localStorage.setItem(DEMO_SEEDED_KEY, "true");
  return seeded;
}

function readEntries(mode: DataMode = "normal"): StoredEntry[] {
  const raw = localStorage.getItem(storageKey(mode));
  if (!raw) return [];

  try {
    const parsed = JSON.parse(raw);
    const candidate = parsed && typeof parsed === "object" ? (parsed as { reports?: unknown }).reports : undefined;
    const entries: StoredEntry[] = Array.isArray(parsed) ? parsed : Array.isArray(candidate) ? candidate as StoredEntry[] : [];
    return entries.map(normalizeStoredEntry).map((entry) => (mode === "demo" ? markDemoEntry(entry) : stripDemoMarker(entry)));
  } catch {
    return [];
  }
}

function writeEntries(entries: StoredEntry[], mode: DataMode = "normal") {
  const normalized = entries.map((entry) => (mode === "demo" ? markDemoEntry(entry) : stripDemoMarker(entry)));
  localStorage.setItem(storageKey(mode), JSON.stringify(normalized));
}

function dateParts(now = new Date()) {
  const localDate = [
    now.getFullYear(),
    String(now.getMonth() + 1).padStart(2, "0"),
    String(now.getDate()).padStart(2, "0")
  ].join("-");

  return {
    report_date: localDate,
    report_time: [
      String(now.getHours()).padStart(2, "0"),
      String(now.getMinutes()).padStart(2, "0"),
      String(now.getSeconds()).padStart(2, "0")
    ].join(":")
  };
}

function baseFields() {
  const now = new Date();
  return {
    id: crypto.randomUUID(),
    created_at: now.toISOString(),
    ...dateParts(now)
  };
}

export function createReport(draft: ReportDraft, demoMode = false): Report {
  if (
    !draft.work_ability ||
    !draft.energy_level ||
    !draft.mental_clarity ||
    !draft.symptom_burden ||
    !draft.effort_strain ||
    !draft.status
  ) {
    throw new Error("Rapporten mangler påkrevde felt.");
  }

  const report: Report = {
    ...baseFields(),
    type: "work_report",
    workplace: draft.workplace?.trim() || undefined,
    work_start_time: draft.work_start_time || undefined,
    work_end_time: draft.work_end_time || undefined,
    work_ability: draft.work_ability,
    energy_level: draft.energy_level,
    mental_clarity: draft.mental_clarity,
    symptom_burden: draft.symptom_burden,
    effort_strain: draft.effort_strain,
    status: draft.status,
    physical_energy: draft.physical_energy_detail ?? draft.energy_level,
    mental_energy: draft.mental_energy_detail ?? draft.energy_level,
    perceived_productivity: draft.perceived_productivity,
    task_completion: draft.task_completion,
    task_types: draft.task_types,
    main_limitations: draft.main_limitations,
    helpful_accommodations: draft.helpful_accommodations,
    note: draft.note.trim() || undefined
  };
  report.total_score_percent = calculateReportScore(report) ?? undefined;

  const mode = modeFromDemoFlag(demoMode);
  const signedReport = demoMode ? markDemoEntry(attachIntegrity(report)) as Report : attachIntegrity(report);
  writeEntries([signedReport, ...readEntries(mode)], mode);
  return signedReport;
}

export function createFollowup(draft: FollowupDraft, demoMode = false): DelayedFollowup {
  if (!draft.worse_than_before) {
    throw new Error("Oppfølgingen mangler svar på om du er verre enn før økten.");
  }
  assertLatestParentIfProvided(draft.parent_report_id || draft.related_report_id, demoMode);

  const followup: DelayedFollowup = {
    ...baseFields(),
    type: "delayed_followup",
    parent_report_id: draft.parent_report_id || draft.related_report_id || undefined,
    related_report_id: draft.related_report_id || draft.parent_report_id || undefined,
    worse_than_before: draft.worse_than_before,
    delayed_symptoms: draft.delayed_symptoms,
    recovery_status: draft.recovery_status,
    note: draft.note.trim() || undefined
  };

  const mode = modeFromDemoFlag(demoMode);
  const signedFollowup = demoMode ? markDemoEntry(attachIntegrity(followup)) as DelayedFollowup : attachIntegrity(followup);
  writeEntries([signedFollowup, ...readEntries(mode)], mode);
  return signedFollowup;
}

export function updateReport(id: string, draft: ReportDraft, demoMode = false): Report {
  const mode = modeFromDemoFlag(demoMode);
  const entries = readEntries(mode);
  const existing = entries.find((entry): entry is Report => entry.id === id && entry.type === "work_report");
  if (!existing) throw new Error("Rapporten finnes ikke.");
  assertEditable(existing);
  if (
    !draft.work_ability ||
    !draft.energy_level ||
    !draft.mental_clarity ||
    !draft.symptom_burden ||
    !draft.effort_strain ||
    !draft.status
  ) {
    throw new Error("Rapporten mangler påkrevde felt.");
  }
  const updated: Report = {
    ...existing,
    workplace: draft.workplace?.trim() || undefined,
    work_start_time: draft.work_start_time || undefined,
    work_end_time: draft.work_end_time || undefined,
    work_ability: draft.work_ability,
    energy_level: draft.energy_level,
    mental_clarity: draft.mental_clarity,
    symptom_burden: draft.symptom_burden,
    effort_strain: draft.effort_strain,
    status: draft.status,
    physical_energy: draft.physical_energy_detail ?? draft.energy_level,
    mental_energy: draft.mental_energy_detail ?? draft.energy_level,
    physical_energy_detail: draft.physical_energy_detail,
    mental_energy_detail: draft.mental_energy_detail,
    perceived_productivity: draft.perceived_productivity,
    task_completion: draft.task_completion,
    task_types: draft.task_types,
    main_limitations: draft.main_limitations,
    helpful_accommodations: draft.helpful_accommodations,
    note: draft.note.trim() || undefined,
    updated_at: new Date().toISOString()
  };
  updated.total_score_percent = calculateReportScore(updated) ?? undefined;
  const signed = demoMode ? markDemoEntry(attachIntegrity(updated)) as Report : attachIntegrity(stripDemoMarker(updated));
  writeEntries(entries.map((entry) => (entry.id === id ? signed : entry)).sort((a, b) => getEntryTime(b) - getEntryTime(a)), mode);
  return signed;
}

export function updateFollowup(id: string, draft: FollowupDraft, demoMode = false): DelayedFollowup {
  const mode = modeFromDemoFlag(demoMode);
  const entries = readEntries(mode);
  const existing = entries.find((entry): entry is DelayedFollowup => entry.id === id && entry.type === "delayed_followup");
  if (!existing) throw new Error("Oppfølgingen finnes ikke.");
  assertEditable(existing);
  if (!draft.worse_than_before) throw new Error("Oppfølgingen mangler svar på om du er verre enn før økten.");
  const parentId = draft.parent_report_id || draft.related_report_id || undefined;
  if (parentId !== (existing.parent_report_id ?? existing.related_report_id)) {
    assertLatestParentIfProvided(parentId, demoMode);
  }
  const updated: DelayedFollowup = {
    ...existing,
    parent_report_id: parentId,
    related_report_id: parentId,
    worse_than_before: draft.worse_than_before,
    delayed_symptoms: draft.delayed_symptoms,
    recovery_status: draft.recovery_status,
    note: draft.note.trim() || undefined,
    updated_at: new Date().toISOString()
  };
  const signed = demoMode ? markDemoEntry(attachIntegrity(updated)) as DelayedFollowup : attachIntegrity(stripDemoMarker(updated));
  writeEntries(entries.map((entry) => (entry.id === id ? signed : entry)).sort((a, b) => getEntryTime(b) - getEntryTime(a)), mode);
  return signed;
}

export function isEditableWithin24Hours(entry: StoredEntry, now = new Date()) {
  const created = new Date(entry.created_at).getTime();
  if (!Number.isFinite(created)) return false;
  return now.getTime() - created <= 24 * 60 * 60 * 1000;
}

export function listReports(demoMode = false): StoredEntry[] {
  if (demoMode) ensureDemoData();
  return readEntries(modeFromDemoFlag(demoMode)).sort((a, b) => getEntryTime(b) - getEntryTime(a));
}

export function getLastWorkReport(demoMode = false): Report | undefined {
  return listReports(demoMode).find((entry): entry is Report => entry.type === "work_report");
}

export function getLatestWorkReport(demoMode = false): Report | undefined {
  return listReports(demoMode)
    .filter((entry): entry is Report => entry.type === "work_report")
    .sort((a, b) => getEntryTime(b) - getEntryTime(a))[0];
}

export function getReport(id: string, demoMode = false): StoredEntry | undefined {
  return readEntries(modeFromDemoFlag(demoMode)).find((entry) => entry.id === id);
}

export function deleteReport(id: string, demoMode = false) {
  const mode = modeFromDemoFlag(demoMode);
  writeEntries(readEntries(mode).filter((entry) => entry.id !== id), mode);
}

export function deleteAllReports(demoMode = false) {
  writeEntries([], modeFromDemoFlag(demoMode));
}

export function exportReports(demoMode = false, range: ExportRange = {}) {
  const reports = filterEntriesForExport(listReports(demoMode), range);
  const payload: ExportPayload = {
    schema_version: 3,
    export_version: 3,
    exported_at: new Date().toISOString(),
    data_mode: demoMode ? "demo" : "normal",
    contains_demo_data: demoMode || reports.some(isDemoEntry),
    export_integrity: createExportIntegrity(reports),
    reports
  };
  return JSON.stringify(payload, null, 2);
}

export function filterEntriesForExport(entries: StoredEntry[], range: ExportRange = {}) {
  assertValidExportRange(range);
  const start = range.fromDate ? new Date(`${range.fromDate}T00:00:00`).getTime() : null;
  const end = range.toDate ? new Date(`${range.toDate}T23:59:59.999`).getTime() : null;
  return entries
    .filter((entry) => {
      const time = getExportEntryTime(entry);
      if (start !== null && time < start) return false;
      if (end !== null && time > end) return false;
      return true;
    })
    .sort((a, b) => getExportEntryTime(b) - getExportEntryTime(a));
}

export function assertValidExportRange(range: ExportRange) {
  if (!range.fromDate || !range.toDate) return;
  const start = new Date(`${range.fromDate}T00:00:00`).getTime();
  const end = new Date(`${range.toDate}T23:59:59.999`).getTime();
  if (Number.isFinite(start) && Number.isFinite(end) && start > end) {
    throw new Error("Fra-dato kan ikke være etter til-dato.");
  }
}

export function buildExportFilename(demoMode = false, range: ExportRange = {}) {
  const prefix = demoMode ? "arbeidspuls-demo" : "arbeidspuls";
  const today = new Date().toISOString().slice(0, 10);
  if (range.fromDate && range.toDate) return `${prefix}-rapporter-${range.fromDate}_til_${range.toDate}.json`;
  if (range.fromDate) return `${prefix}-rapporter-fra_${range.fromDate}.json`;
  if (range.toDate) return `${prefix}-rapporter-til_${range.toDate}.json`;
  return `${prefix}-rapporter-${today}.json`;
}

function getExportEntryTime(entry: StoredEntry) {
  const displayed = Date.parse(`${entry.report_date}T${entry.report_time || "00:00:00"}`);
  if (!Number.isNaN(displayed)) return displayed;
  return getEntryTime(entry);
}

export function previewImportReports(jsonText: string, demoMode = false) {
  const parsed = parseImportJson(jsonText);
  const imported = parseImportEntriesFromParsed(parsed).map(normalizeStoredEntry).map(verifyEntryIntegrity);
  const demoEntriesDetected = countDemoEntries(imported, parsed);
  if (!demoMode && demoEntriesDetected > 0) {
    throw new Error("Importen inneholder demodata. Bytt til demo-modus for å importere den.");
  }
  const mode = modeFromDemoFlag(demoMode);
  const prepared = demoMode ? imported.map(markDemoEntry) : imported.map(stripDemoMarker);
  return {
    ...summarizeImport(prepared, readEntries(mode)),
    demo_entries_detected: demoEntriesDetected,
    verification: summarizeVerification(prepared)
  };
}

export function importReportsFromJson(jsonText: string, demoMode = false): ImportSummary {
  const parsed = parseImportJson(jsonText);
  const imported = parseImportEntriesFromParsed(parsed).map(normalizeStoredEntry).map(verifyEntryIntegrity);
  const demoEntriesDetected = countDemoEntries(imported, parsed);
  if (!demoMode && demoEntriesDetected > 0) {
    throw new Error("Importen inneholder demodata. Bytt til demo-modus for å importere den.");
  }
  const mode = modeFromDemoFlag(demoMode);
  const prepared = demoMode ? imported.map(markDemoEntry) : imported.map(stripDemoMarker);
  const existing = readEntries(mode);
  const summary = {
    ...summarizeImport(prepared, existing),
    demo_entries_detected: demoEntriesDetected,
    verification: summarizeVerification(prepared)
  };
  const existingSignatures = new Set(existing.map(entrySignature));
  const existingById = new Map(existing.map((entry) => [entry.id, entry]));
  const additions: StoredEntry[] = [];

  for (const entry of prepared) {
    const existingWithId = existingById.get(entry.id);
    if (existingWithId && entryContentSignature(existingWithId) !== entryContentSignature(entry)) continue;
    if (existingSignatures.has(entrySignature(entry))) continue;
    existingSignatures.add(entrySignature(entry));
    additions.push(entry);
  }

  writeEntries([...additions, ...existing].sort((a, b) => getEntryTime(b) - getEntryTime(a)), mode);
  return summary;
}

function parseImportEntriesFromParsed(parsed: unknown): StoredEntry[] {
  const candidate = parsed && typeof parsed === "object" ? (parsed as { reports?: unknown }).reports : undefined;
  const entries = Array.isArray(parsed) ? parsed : Array.isArray(candidate) ? candidate : null;
  if (!entries) throw new Error("Filen inneholder ikke en gjenkjennbar rapporteksport.");
  return entries;
}

function parseImportJson(jsonText: string) {
  if (new TextEncoder().encode(jsonText).length > MAX_IMPORT_BYTES) {
    throw new Error("Importfilen er for stor.");
  }
  return JSON.parse(jsonText, (key, value) => (BLOCKED_IMPORT_KEYS.has(key) ? undefined : value));
}

function summarizeImport(imported: StoredEntry[], existing: StoredEntry[]): ImportSummary {
  const existingSignatures = new Set(existing.map(entrySignature));
  const existingById = new Map(existing.map((entry) => [entry.id, entry]));
  const seenImported = new Set<string>();
  const summary: ImportSummary = {
    reports_added: 0,
    followups_added: 0,
    duplicates_skipped: 0,
    conflicts_detected: 0
  };

  for (const entry of imported) {
    const signature = entrySignature(entry);
    const existingWithId = existingById.get(entry.id);
    if (existingWithId && entryContentSignature(existingWithId) !== entryContentSignature(entry)) {
      summary.conflicts_detected += 1;
      continue;
    }
    if (existingSignatures.has(signature) || seenImported.has(signature)) {
      summary.duplicates_skipped += 1;
      continue;
    }
    seenImported.add(signature);
    if (entry.type === "work_report") summary.reports_added += 1;
    if (entry.type === "delayed_followup") summary.followups_added += 1;
  }

  return summary;
}

function entrySignature(entry: Partial<StoredEntry>) {
  if (entry.id) return `id:${entry.id}`;
  return entryContentSignature(entry);
}

function entryContentSignature(entry: Partial<StoredEntry>) {
  if (entry.type === "work_report") {
    const report = entry as Partial<Report>;
    return [
      "report",
      report.created_at,
      report.report_date,
      report.report_time,
      report.work_ability,
      report.energy_level,
      report.status
    ].join("|");
  }
  const followup = entry as Partial<DelayedFollowup>;
  return ["followup", followup.created_at, followup.report_date, followup.report_time, followup.worse_than_before].join("|");
}

function normalizeStoredEntry(entry: StoredEntry): StoredEntry {
  if (entry.type === "work_report") {
    const { physical_energy_detail, mental_energy_detail, ...rest } = entry;
    const report: Report = {
      ...rest,
      task_types: rest.task_types ?? [],
      main_limitations: rest.main_limitations ?? [],
      helpful_accommodations: rest.helpful_accommodations ?? [],
      physical_energy: rest.physical_energy ?? physical_energy_detail ?? rest.energy_level,
      mental_energy: rest.mental_energy ?? mental_energy_detail ?? rest.energy_level
    };
    return {
      ...report,
      total_score_percent: report.total_score_percent ?? calculateReportScore(report) ?? undefined
    };
  }

  return {
    ...entry,
    parent_report_id: entry.parent_report_id ?? entry.related_report_id,
    related_report_id: entry.related_report_id ?? entry.parent_report_id,
    delayed_symptoms: entry.delayed_symptoms ?? []
  };
}

function assertEditable(entry: StoredEntry) {
  if (!isEditableWithin24Hours(entry)) {
    throw new Error("Redigering er bare mulig innen 24 timer etter opprettelse.");
  }
}

function assertLatestParentIfProvided(parentId: string | undefined, demoMode: boolean) {
  if (!parentId) return;
  const latest = getLatestWorkReport(demoMode);
  if (latest && latest.id !== parentId) {
    throw new Error("Oppfølging kan bare opprettes for nyeste rapport.");
  }
}

export function isDemoEntry(entry: Partial<StoredEntry>) {
  return entry.demo === true || typeof entry.id === "string" && entry.id.startsWith("demo-arbeidspuls");
}

function countDemoEntries(entries: StoredEntry[], payload: unknown) {
  const payloadHasDemoFlag =
    !Array.isArray(payload) &&
    typeof payload === "object" &&
    payload !== null &&
    ((payload as ExportPayload).data_mode === "demo" || (payload as ExportPayload).contains_demo_data === true);
  return payloadHasDemoFlag && entries.length > 0 ? Math.max(1, entries.filter(isDemoEntry).length) : entries.filter(isDemoEntry).length;
}

function markDemoEntry<T extends StoredEntry>(entry: T): T {
  return { ...entry, demo: true };
}

function stripDemoMarker<T extends StoredEntry>(entry: T): T {
  const { demo, ...rest } = entry;
  return rest as T;
}
