const assert = require("assert");
const fs = require("fs");
const path = require("path");
const ejs = require("ejs");

const root = path.join(__dirname, "..");
const sandbox = fs.mkdtempSync(path.join(root, ".tmp-lumi-feedback-test-"));
const serviceDir = path.join(sandbox, "src", "services");

fs.mkdirSync(serviceDir, { recursive: true });
for (const file of ["db.js", "feedback.js"]) {
  fs.copyFileSync(path.join(root, "src", "services", file), path.join(serviceDir, file));
}

let database = null;
try {
  database = require(path.join(serviceDir, "db.js"));
  database.migrate();
  const feedback = require(path.join(serviceDir, "feedback.js"));
  const db = database.db;
  const now = Date.now();
  db.prepare(
    "INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)"
  ).run("user-1", "FeedbackUser", now, now);
  db.prepare(
    "INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)"
  ).run("admin-1", "AdminUser", now, now);
  db.prepare(
    "INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)"
  ).run("user-2", "SecondUser", now, now);

  const entry = feedback.createFeedback({
    summary: "Save button fails",
    category: "broken_interaction",
    severity: "broken",
    scope_type: "element",
    scope_label: "Save settings button",
    description: "Clicking the save button does not apply the changes.",
    steps_to_reproduce: "Open settings, change a field, click save.",
    expected_behavior: "The settings should be saved.",
    actual_behavior: "The page reloads without saving.",
    current_url: "http://localhost/admin/settings",
    page_title: "Settings",
    target_metadata: {
      path: "/admin/settings; Settings; Save settings",
      selector: "#save-settings",
      tag: "button",
      text: "Save settings",
      secret: "must not persist"
    },
    diagnostics: {
      user_agent: "verification-agent",
      viewport: "1200x800",
      screenshot_mode: "target",
      similar_feedback_confirmation: "distinct_or_additional_context",
      similar_feedback_ids: "12345678-abcd,invalid id",
      hidden: "must not persist"
    }
  }, { id: "user-1" }, {
    screenshot: {
      path: "feedback/screenshots/test-shot.png",
      mime: "image/png",
      size: 2048
    }
  });

  assert.equal(entry.status, "new");
  assert.equal(entry.scope_label, "/admin/settings;Settings;Save settings");
  assert.equal(entry.target_metadata.secret, undefined);
  assert.equal(entry.diagnostics.hidden, undefined);
  assert.equal(entry.diagnostics.similar_feedback_confirmation, "distinct_or_additional_context");
  assert.equal(entry.diagnostics.similar_feedback_ids, "12345678-abcd");
  assert.equal(entry.screenshot.mime, "image/png");
  assert.equal(feedback.listFeedbackForAdmin({ route: "/admin/settings" })[0].id, entry.id);
  assert.equal(feedback.listFeedbackForAdmin({ target: "save-settings" })[0].id, entry.id);
  assert.equal(feedback.listFeedbackForAdmin({ area: "#save-settings" })[0].id, entry.id);
  assert.equal(feedback.listPublicFeedback({ userId: "user-1" })[0].is_mine, true);
  assert.equal(feedback.listMyFeedback("user-1").length, 1);
  assert.equal(feedback.supportFeedback(entry.id, { id: "user-2" }), 1);
  assert.equal(feedback.listPublicFeedback({ userId: "user-2" })[0].supported_by_me, true);

  feedback.addSubmitterComment(entry.id, "This also happens after a hard reload.", { id: "user-1" });
  feedback.addSubmitterComment(entry.id, "I see this too on the same settings page.", { id: "user-2" });
  const withComment = feedback.getFeedbackForSubmitter(entry.id, "user-1");
  assert.equal(withComment.comments.length, 2);
  assert.equal(withComment.comments[0].kind, "submitter_comment");
  assert.equal(withComment.comments[1].kind, "public_comment");
  const publicViewerEntry = feedback.getFeedbackForViewer(entry.id, "user-2");
  assert.equal(publicViewerEntry.is_mine, false);
  assert.equal(publicViewerEntry.screenshot, null);
  assert(publicViewerEntry.comments.some((comment) => comment.kind === "public_comment"));

  feedback.markFeedbackViewed("user-1");
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2);
  feedback.adminUpdateFeedback(entry.id, {
    status: "needs_more_context",
    category: "bug",
    severity: "urgent",
    admin_reply: "Can you add which browser this happened in?",
    work_note: "Likely settings form regression.",
    status_note: "Need browser detail."
  }, { id: "admin-1" });
  const notices = feedback.notificationSummary("user-1");
  assert.equal(notices.needs_context, 1);
  const adminEntry = feedback.getFeedbackForAdmin(entry.id);
  assert(adminEntry.comments.some((comment) => comment.kind === "admin_reply"));
  assert(adminEntry.comments.some((comment) => comment.kind === "work_note"));
  const submitterEntry = feedback.getFeedbackForSubmitter(entry.id, "user-1");
  assert.equal(submitterEntry.comments.some((comment) => comment.kind === "work_note"), false);

  feedback.adminUpdateFeedback(entry.id, {
    status: "closed",
    category: "bug",
    severity: "urgent",
    status_note: "Finalized after verification."
  }, { id: "admin-1" });
  assert.equal(feedback.getFeedbackForAdmin(entry.id).status, "closed");
  feedback.adminUpdateFeedback(entry.id, {
    status: "reviewed",
    category: "bug",
    severity: "urgent",
    status_note: "Reopened after verification."
  }, { id: "admin-1" });
  assert.equal(feedback.getFeedbackForAdmin(entry.id).status, "reviewed");
  assert(feedback.findSimilarFeedback({
    summary: "Settings save button does nothing",
    category: "bug",
    scope_type: "element",
    current_url: "http://localhost/admin/settings"
  }, { userId: "user-2" }).some((match) => match.id === entry.id));

  const duplicate = feedback.createFeedback({
    summary: "Settings save still fails",
    category: "bug",
    severity: "broken",
    scope_type: "element",
    scope_label: "Save settings button",
    description: "The same settings save control reloads without applying my change.",
    expected_behavior: "The setting should persist.",
    current_url: "http://localhost/admin/settings"
  }, { id: "user-2" });
  feedback.supportFeedback(duplicate.id, { id: "user-1" });
  const merged = feedback.mergeFeedback(duplicate.id, entry.id, {
    reason: "Both reports describe the same settings save failure."
  }, { id: "admin-1" });
  assert.equal(merged.source.status, "duplicate");
  assert.equal(merged.source.merged_into.id, entry.id);
  assert.equal(merged.target.merged_sources[0].id, duplicate.id);
  assert.equal(merged.target.reporter_count, 2);
  assert.equal(merged.target.support_count, 2);
  assert.equal(feedback.getFeedbackForViewer(duplicate.id, "user-2").merged_into.summary, entry.summary);
  assert.equal(feedback.getFeedbackForViewer(entry.id, "user-1").merged_sources[0].submitter_id, undefined);
  feedback.unmergeFeedback(duplicate.id, { reason: "Verification undo." }, { id: "admin-1" });
  assert.equal(feedback.getFeedbackForAdmin(duplicate.id).status, "reviewed");
  feedback.mergeFeedback(duplicate.id, entry.id, {}, { id: "admin-1" });
  assert(feedback.findSimilarFeedback({
    summary: "Controls are weird",
    description: "Clicking the save button does not apply changes after editing settings.",
    category: "bug",
    scope_type: "element",
    current_url: "http://localhost/admin/settings"
  }, { userId: "user-2" }).some((match) => match.id === entry.id));

  const cleanupEntry = feedback.createFeedback({
    summary: "Sensitive screenshot cleanup",
    category: "bug",
    severity: "minor",
    scope_type: "page",
    description: "This report exists to verify cleanup behavior.",
    current_url: "http://localhost/admin/feedback",
    page_title: "Feedback review",
    diagnostics: { user_agent: "sensitive-agent" }
  }, { id: "user-1" }, {
    screenshot: {
      path: "feedback/screenshots/cleanup-shot.png",
      mime: "image/png",
      size: 1024
    },
    attachments: [{
      path: "feedback/attachments/cleanup-note.txt",
      mime: "text/plain",
      size: 128,
      original_name: "cleanup-note.txt"
    }]
  });
  assert.equal(feedback.getFeedbackForAdmin(cleanupEntry.id).attachments.length, 1);
  let cleanedScreenshot = "";
  let cleanedAttachment = "";
  feedback.cleanupFeedback(cleanupEntry.id, {
    clear_screenshot: "1",
    clear_attachments: "1",
    clear_diagnostics: "1",
    clear_target_metadata: "1"
  }, { id: "admin-1" }, {
    deleteScreenshot(relativePath) {
      cleanedScreenshot = relativePath;
    },
    deleteAttachment(relativePath) {
      cleanedAttachment = relativePath;
    }
  });
  const cleaned = feedback.getFeedbackForAdmin(cleanupEntry.id);
  assert.equal(cleaned.screenshot, null);
  assert.deepEqual(cleaned.diagnostics, {});
  assert.deepEqual(cleaned.target_metadata, {});
  assert.equal(cleaned.attachments.length, 0);
  assert.equal(cleanedScreenshot, "feedback/screenshots/cleanup-shot.png");
  assert.equal(cleanedAttachment, "feedback/attachments/cleanup-note.txt");

  const compactExport = feedback.buildFeedbackJobExport({ ids: [entry.id, cleanupEntry.id] });
  assert.equal(compactExport.taskfile.schema, "lumi.feedback.codex_taskfile.v1");
  assert.equal(Array.isArray(compactExport.taskfile.tasks), true);
  assert.equal(compactExport.taskfile.source_feedback[0].ref, "F1");
  assert.equal(compactExport.taskfile.requirements, undefined);
  assert.equal(compactExport.taskfile.context, undefined);
  assert.equal(compactExport.taskfile.debug_only_feedback, undefined);
  assert.equal(JSON.stringify(compactExport.taskfile).includes("http://localhost"), false);
  assert(compactExport.taskfile.tasks.some((task) => task.context?.some((value) => value.includes("Likely settings form regression"))));
  assert.equal(compactExport.debug_export.feedback.length, 2);

  feedback.adminUpdateFeedback(cleanupEntry.id, {
    status: "closed",
    category: cleanupEntry.category,
    severity: cleanupEntry.severity,
    status_note: "Closed for export eligibility verification."
  }, { id: "admin-1" });
  const actionableOnlyExport = feedback.buildFeedbackJobExport({ ids: [entry.id, cleanupEntry.id] });
  assert.equal(actionableOnlyExport.taskfile.source_feedback.length, 1);
  assert.equal(actionableOnlyExport.taskfile.source_feedback[0].id, entry.id);
  assert.equal(actionableOnlyExport.debug_export.feedback.length, 2);

  const feedbackView = path.join(root, "src", "web", "views", "feedback.ejs");
  const adminView = path.join(root, "src", "web", "views", "admin-feedback.ejs");
  const commonLocals = {
    siteTitle: "Lumi Bot",
    assetVersion: "verify",
    theme: null,
    botAvatar: null,
    navSections: [],
    user: { id: "user-1", username: "FeedbackUser" },
    userAvatar: null,
    userInitial: "F",
    platformLogins: [],
    platformLinks: [],
    platforms: [],
    flash: null,
    softError: null,
    feedbackNotifications: feedback.notificationSummary("user-1"),
    feedbackOptions: feedback.feedbackOptions()
  };
  const userRendered = ejs.render(fs.readFileSync(feedbackView, "utf8"), {
    ...commonLocals,
    title: "Feedback",
    feedbackList: feedback.listPublicFeedback({ userId: "user-1" }),
    myFeedback: feedback.listMyFeedback("user-1"),
    selectedFeedback: feedback.getFeedbackForSubmitter(entry.id, "user-1")
  }, { filename: feedbackView });
  assert(userRendered.includes("My feedback"));
  assert(userRendered.includes("Admin reply"));
  assert(userRendered.includes("Open screenshot"));
  assert(userRendered.includes("Community comment"));
  const supporterRendered = ejs.render(fs.readFileSync(feedbackView, "utf8"), {
    ...commonLocals,
    user: { id: "user-2", username: "SecondUser" },
    title: "Feedback",
    feedbackList: feedback.listPublicFeedback({ userId: "user-2" }),
    myFeedback: feedback.listMyFeedback("user-2"),
    selectedFeedback: null
  }, { filename: feedbackView });
  assert(supporterRendered.includes("Marked as affecting you too."));

  const adminRendered = ejs.render(fs.readFileSync(adminView, "utf8"), {
    ...commonLocals,
    user: { id: "admin-1", username: "AdminUser", isAdmin: true },
    userInitial: "A",
    title: "Feedback review",
    feedbackItems: feedback.listFeedbackForAdmin({}),
    feedbackExportAiAvailable: false,
    filters: { status: "", category: "", severity: "", scope: "", area: "", submitter: "", date_from: "", date_to: "", needs_action: "", sort: "last_activity" }
  }, { filename: adminView });
  assert(adminRendered.includes("Feedback queue"));
  assert(!adminRendered.includes("Convert useful reports"));
  assert(adminRendered.includes("Finalize &amp; Close"));
  assert(adminRendered.includes("Delete"));
  assert(adminRendered.includes("Open attached screenshot"));
  assert(adminRendered.includes("Sensitive data cleanup"));
  assert(adminRendered.includes("Remove attachments"));
  assert(adminRendered.includes("Plugin/area"));
  assert(adminRendered.includes("1 also affected"));
  const layoutBottom = fs.readFileSync(path.join(root, "src", "web", "views", "partials", "layout-bottom.ejs"), "utf8");
  const appScript = fs.readFileSync(path.join(root, "src", "web", "public", "app.js"), "utf8");
  assert(layoutBottom.includes("data-feedback-similar-confirm-input"));
  assert(layoutBottom.includes("Mine is distinct or adds useful context"));
  assert(appScript.includes("similar_feedback_confirmation"));
  assert(appScript.includes("similar_feedback_confirmation_required"));

  let deletedScreenshot = "";
  feedback.deleteFeedback(entry.id, {
    deleteScreenshot(relativePath) {
      deletedScreenshot = relativePath;
    }
  });
  assert.equal(feedback.getFeedbackForAdmin(entry.id), null);
  assert.equal(db.prepare("SELECT COUNT(*) AS count FROM feedback_comments WHERE feedback_id = ?").get(entry.id).count, 0);
  assert.equal(db.prepare("SELECT COUNT(*) AS count FROM feedback_status_history WHERE feedback_id = ?").get(entry.id).count, 0);
  assert.equal(deletedScreenshot, "feedback/screenshots/test-shot.png");

  console.log("Core feedback system verification passed.");
} finally {
  database?.db.close();
  fs.rmSync(sandbox, { recursive: true, force: true });
}
