import type { ExportIntegrity, ReportIntegrity, StoredEntry, VerificationStatus, VerificationSummary } from "./types";

const EXCLUDED_KEYS = new Set([
  "integrity",
  "report_integrity",
  "payload_hash_sha256",
  "signature",
  "signed_at",
  "verification_status",
  "verification_reason",
  "verification_error",
  "imported_at",
  "import_source",
  "import_metadata",
  "evaluator_notes",
  "report_review_note",
  "followup_review_notes",
  "person_display_name",
  "local_display_name",
  "ui_state",
  "total_score_percent",
  "score_band",
  "normalized_period_score",
  "demo",
  "data_mode",
  "contains_demo_data"
]);

const ALIAS_FIELDS: Record<string, string> = {
  physical_energy_detail: "physical_energy",
  mental_energy_detail: "mental_energy"
};

export function attachIntegrity<T extends StoredEntry>(entry: T): T {
  const integrity: ReportIntegrity = {
    report_id: entry.id,
    canonicalization: "stable-json-v2",
    algorithm: "sha256",
    payload_hash_sha256: hashEntryPayload(entry),
    signed_at: entry.created_at || new Date().toISOString(),
    verification_status: "valid"
  };
  return { ...entry, integrity };
}

export function verifyEntryIntegrity<T extends StoredEntry>(entry: T): T {
  if (!entry.integrity) {
    return withVerification(entry, "missing", "Rapporten mangler verifiseringsdata. Dette kan være en eldre eksport.");
  }
  if (!["stable-json-v1", "stable-json-v2"].includes(entry.integrity.canonicalization)) {
    return withVerification(entry, "unsupported", "Verifiseringsmetoden støttes ikke av denne appen.");
  }
  if (entry.integrity.algorithm !== "sha256" && entry.integrity.algorithm !== "SHA-256") {
    return withVerification(
      entry,
      "unsupported",
      "Rapporten bruker en eldre verifiseringsmetode som ikke kan kontrolleres sikkert i denne versjonen."
    );
  }

  try {
    const expected = entry.integrity.payload_hash_sha256;
    const actual = hashEntryPayload(entry);
    return expected === actual
      ? withVerification(entry, "valid", "Rapporten matcher lagret integritetsdata.")
      : withVerification(entry, "modified", "Rapporten stemmer ikke med verifiseringsdata. Innholdet kan være endret.");
  } catch {
    return withVerification(entry, "error", "Det oppstod en feil under verifisering.");
  }
}

export function createExportIntegrity(entries: StoredEntry[]): ExportIntegrity {
  const manifest = entries.map((entry) => ({
    id: entry.id,
    type: entry.type,
    payload_hash_sha256: entry.integrity?.payload_hash_sha256 ?? hashEntryPayload(entry)
  }));
  return {
    schema: "arbeidsevne-export-integrity-v1",
    created_at: new Date().toISOString(),
    algorithm: "sha256",
    manifest_hash_sha256: stableHash(manifest),
    note:
      "Lokal best-effort integritetskontroll. Dette kan avdekke endringer i eksporterte data, men er ikke et juridisk eller medisinsk bevis."
  };
}

export function summarizeVerification(entries: StoredEntry[]): VerificationSummary {
  const summary: VerificationSummary = { valid: 0, modified: 0, missing: 0, unsupported: 0, error: 0 };
  for (const entry of entries) {
    const status = entry.integrity?.verification_status ?? "missing";
    summary[status] += 1;
  }
  return summary;
}

export function verificationLabel(status: VerificationStatus | undefined) {
  switch (status) {
    case "valid":
      return "Integritet OK";
    case "modified":
      return "Mulig endret";
    case "unsupported":
      return "Verifisering støttes ikke";
    case "error":
      return "Feil ved verifisering";
    case "missing":
    default:
      return "Mangler lokal hash";
  }
}

export function verificationTooltip(status: VerificationStatus | undefined) {
  switch (status) {
    case "modified":
      return "Rapporten stemmer ikke med verifiseringsdata. Innholdet kan være endret.";
    case "missing":
    case undefined:
      return "Rapporten mangler verifiseringsdata. Dette kan være en eldre eksport eller data uten signering.";
    case "unsupported":
      return "Verifisering støttes ikke for denne rapporten.";
    case "error":
      return "Det oppstod en feil ved verifisering.";
    case "valid":
      return "Dette er en lokal teknisk integritetskontroll. Den kan indikere om eksportdata er endret, men er ikke en garanti for autentisitet.";
  }
}

export function hashEntryPayload(entry: StoredEntry) {
  return sha256(buildCanonicalReportPayload(entry));
}

export function canonicalizeReportForIntegrity(entry: StoredEntry) {
  return buildCanonicalReportPayload(entry);
}

export function buildCanonicalReportPayload(entry: StoredEntry) {
  const clone = clonePlain(entry);
  const normalized = normalizeLegacyAndAliasFields(stripExcluded(clone));
  return stableStringify(stripUndefined(normalized));
}

function withVerification<T extends StoredEntry>(entry: T, status: VerificationStatus, reason: string): T {
  const integrity: ReportIntegrity = entry.integrity
    ? { ...entry.integrity, verification_status: status, verification_reason: reason }
    : {
        report_id: entry.id,
        canonicalization: "stable-json-v2",
        algorithm: "sha256",
        payload_hash_sha256: "",
        signed_at: "",
        verification_status: status,
        verification_reason: reason
      };
  return { ...entry, integrity };
}

function stableHash(value: unknown) {
  return sha256(stableStringify(value));
}

function stableStringify(value: unknown): string {
  if (value === undefined) return "";
  if (value === null || typeof value !== "object") return JSON.stringify(value);
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
  const record = value as Record<string, unknown>;
  return `{${Object.keys(record)
    .filter((key) => !EXCLUDED_KEYS.has(key))
    .sort()
    .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
    .join(",")}}`;
}

function stripExcluded(value: unknown): unknown {
  if (value === null || typeof value !== "object") return value;
  if (Array.isArray(value)) return value.map(stripExcluded);
  return Object.fromEntries(
    Object.entries(value as Record<string, unknown>)
      .filter(([key]) => !EXCLUDED_KEYS.has(key))
      .filter(([, child]) => child !== undefined)
      .map(([key, child]) => [key, stripExcluded(child)])
  );
}

function normalizeLegacyAndAliasFields(value: unknown): unknown {
  if (value === null || typeof value !== "object") return value;
  if (Array.isArray(value)) return value.map(normalizeLegacyAndAliasFields);
  const record = { ...(value as Record<string, unknown>) };
  for (const [alias, preferred] of Object.entries(ALIAS_FIELDS)) {
    if (record[preferred] === undefined && record[alias] !== undefined) {
      record[preferred] = record[alias];
    }
    delete record[alias];
  }
  return Object.fromEntries(Object.entries(record).map(([key, child]) => [key, normalizeLegacyAndAliasFields(child)]));
}

function stripUndefined(value: unknown): unknown {
  if (value === null || typeof value !== "object") return value;
  if (Array.isArray(value)) return value.map(stripUndefined);
  return Object.fromEntries(
    Object.entries(value as Record<string, unknown>)
      .filter(([, child]) => child !== undefined)
      .map(([key, child]) => [key, stripUndefined(child)])
  );
}

function clonePlain<T>(value: T): T {
  return JSON.parse(JSON.stringify(value)) as T;
}

function sha256(input: string) {
  const bytes = new TextEncoder().encode(input);
  const words: number[] = [];
  for (let index = 0; index < bytes.length; index += 1) {
    words[index >> 2] = (words[index >> 2] ?? 0) | (bytes[index] << (24 - (index % 4) * 8));
  }
  const bitLength = bytes.length * 8;
  words[bitLength >> 5] = (words[bitLength >> 5] ?? 0) | (0x80 << (24 - (bitLength % 32)));
  words[(((bitLength + 64) >> 9) << 4) + 15] = bitLength;

  const constants = [
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
  ];
  let h0 = 0x6a09e667;
  let h1 = 0xbb67ae85;
  let h2 = 0x3c6ef372;
  let h3 = 0xa54ff53a;
  let h4 = 0x510e527f;
  let h5 = 0x9b05688c;
  let h6 = 0x1f83d9ab;
  let h7 = 0x5be0cd19;

  for (let block = 0; block < words.length; block += 16) {
    const schedule = new Array<number>(64);
    for (let index = 0; index < 16; index += 1) schedule[index] = words[block + index] ?? 0;
    for (let index = 16; index < 64; index += 1) {
      const s0 = rotateRight(schedule[index - 15], 7) ^ rotateRight(schedule[index - 15], 18) ^ (schedule[index - 15] >>> 3);
      const s1 = rotateRight(schedule[index - 2], 17) ^ rotateRight(schedule[index - 2], 19) ^ (schedule[index - 2] >>> 10);
      schedule[index] = add32(schedule[index - 16], s0, schedule[index - 7], s1);
    }
    let a = h0;
    let b = h1;
    let c = h2;
    let d = h3;
    let e = h4;
    let f = h5;
    let g = h6;
    let h = h7;
    for (let index = 0; index < 64; index += 1) {
      const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
      const ch = (e & f) ^ (~e & g);
      const temp1 = add32(h, s1, ch, constants[index], schedule[index]);
      const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
      const maj = (a & b) ^ (a & c) ^ (b & c);
      const temp2 = add32(s0, maj);
      h = g;
      g = f;
      f = e;
      e = add32(d, temp1);
      d = c;
      c = b;
      b = a;
      a = add32(temp1, temp2);
    }
    h0 = add32(h0, a);
    h1 = add32(h1, b);
    h2 = add32(h2, c);
    h3 = add32(h3, d);
    h4 = add32(h4, e);
    h5 = add32(h5, f);
    h6 = add32(h6, g);
    h7 = add32(h7, h);
  }

  return [h0, h1, h2, h3, h4, h5, h6, h7].map((value) => value.toString(16).padStart(8, "0")).join("");
}

function rotateRight(value: number, amount: number) {
  return (value >>> amount) | (value << (32 - amount));
}

function add32(...values: number[]) {
  return values.reduce((sum, value) => (sum + value) >>> 0, 0);
}
