const fs = require("fs");
const { writeJsonAtomicSync } = require("../../../src/services/safe-files");
const { resolveData } = require("./paths");
const { roleOf } = require("./permissions");

const FEEDBACK_TAGS = Object.freeze([
  "good",
  "bad",
  "wrong_link",
  "hallucinated",
  "too_generic",
  "unsafe",
  "should_clarify",
  "bad_code",
  "wrong_scope",
  "wrong_tool_usage",
  "bad_retrieval",
  "bad_response",
  "missing_knowledge"
]);

const FEEDBACK_KINDS = Object.freeze(["instruction_based", "strict_correction"]);
const FINAL_FEEDBACK_STATUSES = Object.freeze(["implemented", "rejected", "reviewed", "archived"]);

class FeedbackStore {
  constructor(options = {}) {
    this.file = options.file || resolveData("feedback", "reviews.json");
  }

  capture(input, actor) {
    const tag = FEEDBACK_TAGS.includes(input.feedback_tag) ? input.feedback_tag : null;
    if (!tag) throw new Error("Unknown feedback tag.");
    const entry = {
      id: crypto.randomUUID(),
      user_message: clean(input.user_message, 6000),
      assistant_answer: clean(input.assistant_answer, 16000),
      route_used: clean(input.route_used, 120),
      role: normalizeRole(input.role),
      origin: clean(input.origin, 80) || "webui",
      platform: clean(input.platform, 80) || "webui",
      model: clean(input.model, 200),
      timestamp: validDate(input.timestamp) || new Date().toISOString(),
      rating: input.rating === "up" || input.feedback_tag === "good" ? "up" : "down",
      feedback_tag: tag,
      feedback_kind: FEEDBACK_KINDS.includes(input.feedback_kind)
        ? input.feedback_kind
        : "instruction_based",
      optional_correction: clean(input.optional_correction, 16000),
      context_snapshot: normalizeContextSnapshot(input.context_snapshot),
      linked_okf_correction: null,
      status: "pending",
      submitted_by: String(actor?.id || "anonymous"),
      reviewed_by: null,
      reviewed_at: null,
      verified_by: null,
      verified_at: null,
      review_notes: "",
      export_approved: false
    };
    if (!entry.user_message || !entry.assistant_answer) {
      throw new Error("Feedback requires the user message and assistant answer.");
    }
    const store = this.read();
    store.entries.unshift(entry);
    this.write(store);
    return entry;
  }

  list({ page = 1, pageSize = 20, status = "", viewerRole = "admin", scope = "all", query = "" } = {}) {
    const search = clean(query, 300).toLowerCase();
    const filtered = this.read().entries
      .filter((entry) => roleRank(viewerRole) >= roleRank(entry.role))
      .filter((entry) => scope === "active" ? !isFinalized(entry) : scope === "history" ? isFinalized(entry) : true)
      .filter((entry) => !status || entry.status === status)
      .filter((entry) => !search || searchableFeedback(entry).includes(search));
    return paginate(filtered, page, pageSize);
  }

  counts(viewerRole = "admin") {
    const visible = this.read().entries.filter((entry) => roleRank(viewerRole) >= roleRank(entry.role));
    const active = visible.filter((entry) => !isFinalized(entry));
    return {
      active: active.length,
      pending: active.filter((entry) => entry.status === "pending").length,
      needs_attention: active.filter((entry) => ["flagged", "verified", "approved"].includes(entry.status)).length,
      history: visible.length - active.length
    };
  }

  all() {
    return this.read().entries;
  }

  get(id) {
    return this.read().entries.find((entry) => entry.id === id) || null;
  }

  edit(id, values, actor) {
    return this.mutate(id, (entry) => ({
      ...entry,
      feedback_tag: FEEDBACK_TAGS.includes(values.feedback_tag) ? values.feedback_tag : entry.feedback_tag,
      feedback_kind: FEEDBACK_KINDS.includes(values.feedback_kind) ? values.feedback_kind : entry.feedback_kind || "instruction_based",
      optional_correction: clean(values.optional_correction, 16000),
      review_notes: clean(values.review_notes, 4000),
      reviewed_by: String(actor.id),
      reviewed_at: new Date().toISOString()
    }));
  }

  setStatus(id, status, actor, notes = "") {
    if (!["pending", "flagged", "verified", "approved", "implemented", "rejected", "reviewed", "archived"].includes(status)) {
      throw new Error("Invalid review status.");
    }
    return this.mutate(id, (entry) => ({
      ...entry,
      status,
      review_notes: clean(notes, 4000) || entry.review_notes,
      reviewed_by: String(actor.id),
      reviewed_at: new Date().toISOString()
    }));
  }

  markImplemented(id, actor, details = {}) {
    return this.mutate(id, (entry) => ({
      ...entry,
      status: "implemented",
      implementation_target: clean(details.target, 80) || "correction",
      implementation_id: clean(details.implementation_id, 200),
      implementation_summary: clean(details.summary, 1000),
      implemented_by: String(actor.id),
      implemented_at: new Date().toISOString(),
      reviewed_by: String(actor.id),
      reviewed_at: new Date().toISOString()
    }));
  }

  restoreToQueue(id, actor) {
    return this.mutate(id, (entry) => ({
      ...entry,
      status: "pending",
      restored_by: String(actor.id),
      restored_at: new Date().toISOString(),
      reviewed_by: String(actor.id),
      reviewed_at: new Date().toISOString()
    }));
  }

  verify(id, actor, notes = "") {
    return this.mutate(id, (entry) => ({
      ...entry,
      status: entry.status === "rejected" ? "rejected" : "verified",
      review_notes: clean(notes, 4000) || entry.review_notes,
      verified_by: String(actor.id),
      verified_at: new Date().toISOString()
    }));
  }

  markExportApproved(id, actor) {
    return this.mutate(id, (entry) => ({
      ...entry,
      export_approved: true,
      reviewed_by: entry.reviewed_by || String(actor.id),
      reviewed_at: entry.reviewed_at || new Date().toISOString()
    }));
  }

  linkOkfCorrection(id, correctionPath, actor) {
    return this.mutate(id, (entry) => ({
      ...entry,
      linked_okf_correction: clean(correctionPath, 1000),
      reviewed_by: entry.reviewed_by || String(actor.id),
      reviewed_at: new Date().toISOString()
    }));
  }

  delete(id) {
    const store = this.read();
    const before = store.entries.length;
    store.entries = store.entries.filter((entry) => entry.id !== id);
    if (store.entries.length === before) return false;
    this.write(store);
    return true;
  }

  mutate(id, updater) {
    const store = this.read();
    const index = store.entries.findIndex((entry) => entry.id === id);
    if (index < 0) throw new Error("Feedback review was not found.");
    store.entries[index] = updater(store.entries[index]);
    this.write(store);
    return store.entries[index];
  }

  read() {
    try {
      const parsed = JSON.parse(fs.readFileSync(this.file, "utf8"));
      return { entries: Array.isArray(parsed.entries) ? parsed.entries : [] };
    } catch {
      return { entries: [] };
    }
  }

  write(store) {
    atomicJson(this.file, { entries: store.entries.slice(0, 5000) });
  }
}

function improvementAccess(user, config = {}) {
  const role = roleOf(user);
  const improvement = config.improvement || {};
  const trusted = role === "mod" &&
    (improvement.trusted_moderator_reviewers || []).map(String).includes(String(user?.id));
  const allowed = role === "admin" ||
    (role === "mod" && improvement.allow_moderators_to_review_responses === true);
  return {
    allowed,
    role,
    trusted,
    can_submit: allowed,
    can_flag: allowed,
    can_verify: role === "admin" || trusted,
    can_approve: role === "admin",
    can_edit: role === "admin",
    can_delete: role === "admin",
    can_implement: role === "admin",
    can_export: role === "admin",
    can_run_evals: role === "admin"
  };
}

function paginate(rows, pageValue, pageSizeValue) {
  const pageSize = Math.max(1, Math.min(100, Number.parseInt(pageSizeValue, 10) || 20));
  const pages = Math.max(1, Math.ceil(rows.length / pageSize));
  const page = Math.min(pages, Math.max(1, Number.parseInt(pageValue, 10) || 1));
  const start = (page - 1) * pageSize;
  return { entries: rows.slice(start, start + pageSize), page, pages, page_size: pageSize, total: rows.length };
}

function atomicJson(file, value) {
  writeJsonAtomicSync(file, value);
}

function clean(value, max) {
  return String(value || "").trim().slice(0, max);
}

function normalizeRole(value) {
  return ["admin", "mod", "user"].includes(value) ? value : "user";
}

function validDate(value) {
  const date = new Date(value);
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
}

function normalizeContextSnapshot(value) {
  const snapshot = value && typeof value === "object" ? value : {};
  const retrieval = snapshot.retrieval && typeof snapshot.retrieval === "object" ? snapshot.retrieval : {};
  return {
    retrieval: {
      query: clean(retrieval.query, 500),
      candidate_count: safeCount(retrieval.candidate_count),
      selected_count: safeCount(retrieval.selected_count),
      depth: clean(retrieval.depth, 40)
    },
    okf: cleanList(snapshot.okf, 4, 1200),
    corrections: cleanList(snapshot.corrections, 3, 1200),
    repository: cleanList(snapshot.repository, 3, 1200),
    tools: cleanList(snapshot.tools, 20, 120)
  };
}

function cleanList(value, limit, maxLength) {
  return (Array.isArray(value) ? value : []).slice(0, limit).map((entry) => clean(entry, maxLength)).filter(Boolean);
}

function safeCount(value) {
  const number = Number(value);
  return Number.isFinite(number) ? Math.max(0, Math.min(100000, Math.floor(number))) : 0;
}

function roleRank(role) {
  return { user: 1, mod: 2, admin: 3 }[normalizeRole(role)] || 1;
}

function isFinalized(entry) {
  return FINAL_FEEDBACK_STATUSES.includes(entry?.status);
}

function searchableFeedback(entry) {
  return [
    entry.id,
    entry.user_message,
    entry.assistant_answer,
    entry.optional_correction,
    entry.review_notes,
    entry.feedback_tag,
    entry.feedback_kind,
    entry.status,
    entry.implementation_target,
    entry.implementation_summary,
    entry.linked_okf_correction,
    entry.platform,
    entry.route_used
  ].map((value) => String(value || "").toLowerCase()).join("\n");
}

module.exports = {
  FEEDBACK_KINDS,
  FEEDBACK_TAGS,
  FINAL_FEEDBACK_STATUSES,
  FeedbackStore,
  improvementAccess,
  isFinalized,
  normalizeContextSnapshot,
  paginate,
  atomicJson
};
