import { calculateReportScore, getEntryTime } from "./scoring";
import { summarizeVerification, verifyEntryIntegrity } from "./integrity";
import { demoEvaluatorProfiles } from "./demoData";
import type { EvaluatorNote, EvaluatorProfile, ExportPayload, ImportTargetResolution, Report, StoredEntry } from "./types";

const EVALUATOR_KEY = "arbeidsevne-egenvurdering:evaluator-profiles";
const DEMO_EVALUATOR_KEY = "arbeidspuls:demo-evaluator-profiles";
const DEMO_EVALUATOR_SEEDED_KEY = "arbeidspuls:demo-evaluator-seeded";
const MAX_IMPORT_BYTES = 5 * 1024 * 1024;
const BLOCKED_IMPORT_KEYS = new Set(["__proto__", "constructor", "prototype"]);

export type ImportResult = {
  profile: EvaluatorProfile;
  added: number;
  skipped: number;
  verification: ReturnType<typeof summarizeVerification>;
  demo_entries_detected: number;
  target_resolution: ImportTargetResolution;
  matched_person_id: string | null;
  matched_report_ids: string[];
};

function evaluatorKey(demoMode = false) {
  return demoMode ? DEMO_EVALUATOR_KEY : EVALUATOR_KEY;
}

export function ensureDemoEvaluatorProfiles() {
  const existing = readProfiles(true);
  if (existing.length > 0) return existing;
  if (localStorage.getItem(DEMO_EVALUATOR_SEEDED_KEY) === "true") return [];
  const seeded = demoEvaluatorProfiles.map(normalizeProfile);
  writeProfiles(seeded, true);
  localStorage.setItem(DEMO_EVALUATOR_SEEDED_KEY, "true");
  return seeded;
}

function readProfiles(demoMode = false): EvaluatorProfile[] {
  const raw = localStorage.getItem(evaluatorKey(demoMode));
  if (!raw) return [];

  try {
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

function writeProfiles(profiles: EvaluatorProfile[], demoMode = false) {
  localStorage.setItem(evaluatorKey(demoMode), JSON.stringify(profiles));
}

export function listEvaluatorProfiles(demoMode = false) {
  if (demoMode) ensureDemoEvaluatorProfiles();
  return readProfiles(demoMode).map(normalizeProfile).sort((a, b) => b.updated_at.localeCompare(a.updated_at));
}

export function updateEvaluatorProfile(profile: EvaluatorProfile, demoMode = false) {
  const profiles = readProfiles(demoMode);
  const next = profiles.some((item) => item.id === profile.id)
    ? profiles.map((item) => (item.id === profile.id ? profile : item))
    : [profile, ...profiles];
  writeProfiles(next, demoMode);
}

export function renameEvaluatorProfile(profileId: string, displayName: string, demoMode = false) {
  const now = new Date().toISOString();
  const profiles = readProfiles(demoMode).map((profile) =>
    profile.id === profileId ? { ...profile, display_name: displayName.trim() || profile.display_name, updated_at: now } : profile
  );
  writeProfiles(profiles, demoMode);
}

export function deleteEvaluatorProfile(profileId: string, demoMode = false) {
  writeProfiles(readProfiles(demoMode).filter((profile) => profile.id !== profileId), demoMode);
}

export function addEvaluatorNote(profileId: string, text: string, relatedReportId?: string) {
  return upsertEvaluatorNote(profileId, text, { note_type: relatedReportId ? "report" : "person", related_report_id: relatedReportId });
}

export function upsertEvaluatorNote(
  profileId: string,
  text: string,
  noteTarget: Pick<EvaluatorNote, "note_type" | "related_report_id" | "related_followup_id" | "parent_report_id">,
  demoMode = false
) {
  const now = new Date().toISOString();
  const trimmed = text.trim();
  const profiles = readProfiles(demoMode).map((profile) =>
    profile.id === profileId ? upsertNoteInProfile(normalizeProfile(profile), trimmed, noteTarget, now) : profile
  );
  writeProfiles(profiles, demoMode);
}

export function deleteEvaluatorNote(
  profileId: string,
  noteTarget: Pick<EvaluatorNote, "note_type" | "related_report_id" | "related_followup_id">,
  demoMode = false
) {
  const now = new Date().toISOString();
  const profiles = readProfiles(demoMode).map((profile) =>
    profile.id === profileId
      ? {
          ...normalizeProfile(profile),
          evaluator_notes: normalizeProfile(profile).evaluator_notes.filter((note) => !sameNoteTarget(note, noteTarget)),
          updated_at: now
        }
      : profile
  );
  writeProfiles(profiles, demoMode);
}

export function importExportPayload(jsonText: string, displayName: string, targetProfileId?: string, demoMode = false): ImportResult {
  const parsed = parseImportJson(jsonText);
  const reports = extractReports(parsed).map(verifyEntryIntegrity);
  const demoEntriesDetected = countDemoEntries(reports, parsed);
  const profiles = readProfiles(demoMode).map(normalizeProfile);
  const fingerprint = makeFingerprint(reports);
  const target = resolveImportTarget(reports, profiles, targetProfileId);
  if (target.action === "conflict_requires_manual_resolution") {
    const names = target.matches.map((match) => `${match.person.display_name} (${match.matchCount})`).join(", ");
    throw new Error(`Importen matcher flere brukere og må avklares manuelt: ${names}.`);
  }
  const targetProfile = target.person;
  const now = new Date().toISOString();

  const existingReports = targetProfile?.reports ?? [];
  const existingKeys = new Set(existingReports.map(reportSignature));
  const nextReports: StoredEntry[] = [...existingReports];
  let added = 0;
  let skipped = 0;

  for (const report of reports) {
    const normalized = normalizeImportedEntry(report);
    const signature = reportSignature(normalized);
    if (existingKeys.has(signature)) {
      skipped += 1;
      continue;
    }
    existingKeys.add(signature);
    nextReports.push(normalized);
    added += 1;
  }

  const profile: EvaluatorProfile = targetProfile
    ? {
        ...targetProfile,
        display_name: targetProfile.display_name,
        updated_at: now,
        source_fingerprint: targetProfile.source_fingerprint ?? fingerprint,
        contains_demo_data: targetProfile.contains_demo_data || demoEntriesDetected > 0 || nextReports.some(isDemoEntry),
        reports: nextReports.sort((a, b) => getEntryTime(b) - getEntryTime(a))
      }
    : {
        id: crypto.randomUUID(),
        display_name: displayName.trim() || `Import ${new Date().toLocaleDateString("nb-NO")}`,
        created_at: now,
        updated_at: now,
        source_fingerprint: fingerprint,
        contains_demo_data: demoEntriesDetected > 0 || nextReports.some(isDemoEntry),
        reports: nextReports.sort((a, b) => getEntryTime(b) - getEntryTime(a)),
        evaluator_notes: []
      };

  updateEvaluatorProfile(profile, demoMode);
  return {
    profile,
    added,
    skipped,
    verification: summarizeVerification(reports),
    demo_entries_detected: demoEntriesDetected,
    target_resolution: targetProfile ? "matched_existing_person" : "created_new_person",
    matched_person_id: targetProfile?.id ?? null,
    matched_report_ids: target.matchedReportIds
  };
}

export function exportEvaluatorBackup(demoMode = false) {
  return JSON.stringify(
    {
      backup_type: "arbeidsevne-veilederdata",
      schema_version: 1,
      exported_at: new Date().toISOString(),
      data_mode: demoMode ? "demo" : "normal",
      profiles: listEvaluatorProfiles(demoMode)
    },
    null,
    2
  );
}

export function importEvaluatorBackup(jsonText: string, demoMode = false) {
  const parsed = parseImportJson(jsonText);
  if (parsed?.backup_type !== "arbeidsevne-veilederdata" || !Array.isArray(parsed.profiles)) {
    throw new Error("Filen er ikke en gyldig veilederbackup.");
  }
  const existing = readProfiles(demoMode);
  const merged = [...existing];
  for (const profile of parsed.profiles as EvaluatorProfile[]) {
    const index = merged.findIndex((item) => item.id === profile.id);
    if (index >= 0) {
      merged[index] = {
        ...normalizeProfile(merged[index]),
        ...normalizeProfile(profile),
        display_name: normalizeProfile(merged[index]).display_name || normalizeProfile(profile).display_name
      };
    } else {
      merged.push(normalizeProfile(profile));
    }
  }
  writeProfiles(merged, demoMode);
}

export function extractReports(payload: ExportPayload | StoredEntry[] | { reports?: StoredEntry[] }) {
  if (Array.isArray(payload)) return payload;
  if (payload && Array.isArray(payload.reports)) return payload.reports;
  throw new Error("Filen inneholder ikke en gjenkjennbar rapporteksport.");
}

export function reportSignature(entry: Partial<StoredEntry>) {
  if (entry.id) return `id:${entry.id}`;
  const values =
    entry.type === "work_report"
      ? [
          entry.created_at,
          entry.report_date,
          entry.report_time,
          (entry as Partial<Report>).work_ability,
          (entry as Partial<Report>).energy_level,
          (entry as Partial<Report>).status
        ]
      : [entry.created_at, entry.report_date, entry.report_time, entry.type];
  return `fallback:${values.join("|")}`;
}

function normalizeImportedEntry(entry: StoredEntry): StoredEntry {
  if (entry.type === "work_report") {
    const { physical_energy_detail, mental_energy_detail, ...rest } = entry;
    return {
      ...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,
      total_score_percent: rest.total_score_percent ?? calculateReportScore({ ...rest, physical_energy: rest.physical_energy ?? physical_energy_detail ?? rest.energy_level, mental_energy: rest.mental_energy ?? mental_energy_detail ?? rest.energy_level }) ?? 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 normalizeProfile(profile: EvaluatorProfile): EvaluatorProfile {
  const reports = (profile.reports ?? []).map(normalizeImportedEntry).sort((a, b) => getEntryTime(b) - getEntryTime(a));
  return {
    ...profile,
    reports,
    contains_demo_data: profile.contains_demo_data || reports.some(isDemoEntry),
    evaluator_notes: (profile.evaluator_notes ?? []).map((note) => ({
      ...note,
      note_type: note.note_type ?? (note.related_report_id ? "report" : note.related_followup_id ? "followup" : "person")
    }))
  };
}

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 upsertNoteInProfile(
  profile: EvaluatorProfile,
  text: string,
  noteTarget: Pick<EvaluatorNote, "note_type" | "related_report_id" | "related_followup_id" | "parent_report_id">,
  now: string
): EvaluatorProfile {
  const notes = profile.evaluator_notes.filter((note) => !sameNoteTarget(note, noteTarget));
  if (!text) {
    return { ...profile, evaluator_notes: notes, updated_at: now };
  }
  const existing = profile.evaluator_notes.find((note) => sameNoteTarget(note, noteTarget));
  const note: EvaluatorNote = {
    id: existing?.id ?? crypto.randomUUID(),
    created_at: existing?.created_at ?? now,
    updated_at: now,
    text,
    note_type: noteTarget.note_type,
    related_report_id: noteTarget.related_report_id,
    related_followup_id: noteTarget.related_followup_id,
    parent_report_id: noteTarget.parent_report_id
  };
  return { ...profile, evaluator_notes: [note, ...notes], updated_at: now };
}

function sameNoteTarget(note: Pick<EvaluatorNote, "note_type" | "related_report_id" | "related_followup_id">, target: Pick<EvaluatorNote, "note_type" | "related_report_id" | "related_followup_id">) {
  return (
    (note.note_type ?? "person") === (target.note_type ?? "person") &&
    (note.related_report_id ?? "") === (target.related_report_id ?? "") &&
    (note.related_followup_id ?? "") === (target.related_followup_id ?? "")
  );
}

function makeFingerprint(reports: StoredEntry[]) {
  const base = reports
    .slice()
    .sort((a, b) => reportSignature(a).localeCompare(reportSignature(b)))
    .map(reportSignature)
    .join(";");
  let hash = 0;
  for (let index = 0; index < base.length; index += 1) {
    hash = (hash * 31 + base.charCodeAt(index)) >>> 0;
  }
  return hash.toString(16);
}

function resolveImportTarget(reports: StoredEntry[], profiles: EvaluatorProfile[], targetProfileId?: string) {
  const importedIds = new Set(reports.map((report) => report.id).filter(Boolean));
  if (importedIds.size === 0 && targetProfileId) {
    return {
      action: "matched_existing_person" as const,
      person: profiles.find((profile) => profile.id === targetProfileId),
      matchedReportIds: [] as string[]
    };
  }

  const matches = profiles
    .map((person) => {
      const personIds = new Set(person.reports.map((report) => report.id).filter(Boolean));
      const matchedReportIds = [...importedIds].filter((id) => personIds.has(id));
      return { person, matchCount: matchedReportIds.length, matchedReportIds };
    })
    .filter((match) => match.matchCount > 0)
    .sort((a, b) => b.matchCount - a.matchCount);

  if (matches.length === 0) return { action: "created_new_person" as const, person: undefined, matchedReportIds: [] as string[] };
  if (matches.length === 1) {
    return {
      action: "matched_existing_person" as const,
      person: matches[0].person,
      matchedReportIds: matches[0].matchedReportIds
    };
  }
  return {
    action: "conflict_requires_manual_resolution" as const,
    matches,
    matchedReportIds: matches.flatMap((match) => match.matchedReportIds)
  };
}

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));
}
