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

const root = path.join(__dirname, "..");

function listFiles(directory, extension, output = []) {
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
    const target = path.join(directory, entry.name);
    if (entry.isDirectory()) listFiles(target, extension, output);
    else if (target.endsWith(extension)) output.push(target);
  }
  return output;
}

function verifyViews() {
  const viewRoots = [path.join(root, "src", "web", "views"), path.join(root, "plugins")];
  const files = viewRoots.flatMap((directory) => listFiles(directory, ".ejs"));
  for (const file of files) {
    ejs.compile(fs.readFileSync(file, "utf8"), { filename: file });
  }
  return files.length;
}

function verifyThemeService() {
  const sandbox = fs.mkdtempSync(path.join(root, ".tmp-lumi-theme-test-"));
  const serviceDir = path.join(sandbox, "src", "services");
  let database = null;
  fs.mkdirSync(serviceDir, { recursive: true });
  for (const file of ["config.js", "db.js", "settings.js", "themes.js"]) {
    fs.copyFileSync(
      path.join(root, "src", "services", file),
      path.join(serviceDir, file)
    );
  }

  try {
    database = require(path.join(serviceDir, "db.js"));
    database.migrate();
    require(path.join(serviceDir, "settings.js")).ensureDefaults();
    const themes = require(path.join(serviceDir, "themes.js"));

    assert.strictEqual(themes.BUILTIN_THEMES.length, 6);
    for (const theme of themes.BUILTIN_THEMES) {
      assert.deepStrictEqual(themes.validateThemeValues(theme), []);
    }

    const library = themes.listThemes();
    assert(library.every((theme) => theme.builtin));
    assert.throws(
      () => themes.saveCustomTheme("builtin:lumi-default", library[0]),
      /read-only/
    );

    const copy = themes.duplicateTheme("builtin:midnight", "Verification Theme");
    assert.strictEqual(copy.builtin, false);
    assert.strictEqual(copy.typography.bodyFont, "lumi");
    themes.setActiveTheme(copy.id);
    assert.strictEqual(themes.getActiveTheme().id, copy.id);

    const invalid = JSON.parse(JSON.stringify(copy));
    invalid.light.text = "not-a-color";
    assert.throws(() => themes.saveCustomTheme(copy.id, invalid), /hex color/);
    invalid.light.text = copy.light.text;
    invalid.typography.bodyFont = "external-css";
    assert.throws(() => themes.saveCustomTheme(copy.id, invalid), /font preset/);

    const renamed = themes.renameCustomTheme(copy.id, "Verified Theme");
    assert.strictEqual(renamed.name, "Verified Theme");

    const repaired = themes.normalizeThemeValues({
      ...renamed,
      light: { ...renamed.light, text: "#ffffff", surface: "#ffffff" }
    });
    assert.notStrictEqual(repaired.light.text, repaired.light.surface);

    const themeView = path.join(root, "src", "web", "views", "admin-theme.ejs");
    const rendered = ejs.render(fs.readFileSync(themeView, "utf8"), {
      title: "Theming",
      siteTitle: "Lumi Bot",
      assetVersion: "verify",
      theme: renamed,
      activeTheme: renamed,
      themes: themes.listThemes(),
      editingTheme: renamed,
      editingBaseTheme: themes.getThemeById(renamed.baseThemeId),
      fontStacks: themes.FONT_STACKS,
      botAvatar: null,
      navSections: [],
      user: { username: "Admin" },
      userAvatar: null,
      userInitial: "A",
      platformLogins: [],
      flash: null,
      softError: null
    }, { filename: themeView });
    assert(rendered.includes("data-theme-editor"));
    assert(rendered.includes("data-theme-font"));
    assert(rendered.includes("data-theme-popout"));
    assert(rendered.includes("data-lumi-state-button"));
    assert(rendered.includes("Built-in &middot; read-only"));

    const homeView = path.join(root, "src", "web", "views", "home.ejs");
    const homeRendered = ejs.render(fs.readFileSync(homeView, "utf8"), {
      title: "Home",
      siteTitle: "Lumi Bot",
      assetVersion: "verify",
      theme: renamed,
      botAvatar: null,
      navSections: [],
      user: { username: "Admin" },
      userAvatar: null,
      userInitial: "A",
      platformLogins: [],
      flash: null,
      softError: null,
      homepageLinks: [{
        label: "Twitch",
        description: "Watch live",
        url: "https://example.com",
        fallback_icon: "T"
      }],
      homepageHero: {
        type: "static_image",
        title: "Featured",
        description: "Featured content",
        image_url: "https://example.com/hero.png",
        source_url: "https://example.com"
      }
    }, { filename: homeView });
    assert(homeRendered.includes("homepage-link-button"));
    assert(homeRendered.includes("homepage-dynamic-hero"));

    const loginView = path.join(root, "src", "web", "views", "localhost-login.ejs");
    const loginRendered = ejs.render(fs.readFileSync(loginView, "utf8"), {
      title: "Localhost Login",
      username: "admin",
      siteTitle: "Lumi Bot",
      assetVersion: "verify",
      theme: renamed,
      botAvatar: null,
      navSections: [],
      user: null,
      userAvatar: null,
      userInitial: "",
      platformLogins: [{ id: "localhost", label: "Localhost Login", configured: true, loginPath: "/auth/localhost" }],
      flash: null,
      softError: null
    }, { filename: loginView });
    assert(loginRendered.includes("admin / admin"));

    const statusView = path.join(root, "plugins", "moderation", "views", "status.ejs");
    const statusRendered = ejs.render(fs.readFileSync(statusView, "utf8"), {
      title: "Access restricted",
      assetVersion: "verify",
      theme: renamed,
      sanction: {
        action_type: "timeout",
        status: "active",
        created_at: Date.now(),
        expires_at: Date.now() + 60000,
        reason_short: "Verification",
        reason_detail: "Standalone themed view verification.",
        created_by_name: "Admin"
      }
    }, { filename: statusView });
    assert(statusRendered.includes("/lumi-components.css"));
    assert(statusRendered.includes('data-theme-id="custom:'));

    themes.deleteCustomTheme(copy.id);
    assert.strictEqual(themes.getActiveTheme().id, themes.DEFAULT_THEME_ID);
  } finally {
    database?.db.close();
    fs.rmSync(sandbox, { recursive: true, force: true });
  }
}

function verifyUpdateCachePreserved() {
  const updateManagerPath = path.join(root, "src", "services", "update-manager.js");
  const source = fs.readFileSync(updateManagerPath, "utf8");
  const generatedPaths = source.match(/const GENERATED_RELATIVE_PATHS = new Set\(\[([\s\S]*?)\]\);/);
  assert(generatedPaths, "Could not find generated path cleanup list.");
  assert(
    !generatedPaths[1].includes('"data/update-cache"') &&
      !generatedPaths[1].includes("'data/update-cache'"),
    "Core update cleanup must not delete data/update-cache because repo updates apply from data/update-cache/repo."
  );
}

function verifySharedUpdateActions() {
  const appSource = fs.readFileSync(path.join(root, "src", "web", "public", "app.js"), "utf8");
  const updateSource = fs.readFileSync(path.join(root, "src", "web", "public", "update-management.js"), "utf8");
  const serverSource = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
  const updates = fs.readFileSync(path.join(root, "src", "web", "views", "admin-updates.ejs"), "utf8");
  const dashboard = fs.readFileSync(path.join(root, "src", "web", "views", "admin-dashboard.ejs"), "utf8");
  const settings = fs.readFileSync(path.join(root, "src", "web", "views", "admin-settings.ejs"), "utf8");
  assert(updateSource.includes('submitter?.matches?.("[data-update-action]")'));
  assert(appSource.includes('form?.getAttribute?.("action")'));
  assert(appSource.includes("window.LumiForms = Object.freeze"));
  assert(appSource.includes('form?.getAttribute?.("method")'));
  assert(!appSource.includes("submitter?.formAction || form.action"));
  assert(updateSource.includes('form.matches("[data-update-check-form]")'));
  assert(dashboard.includes('action="/admin/check-update" class="inline-form" data-update-action'));
  assert(settings.includes('formaction=\\"/admin/check-update\\" formmethod=\\"post\\" data-update-action'));
  assert(serverSource.includes('sendUpdateResult(req, res, {\n        status,'));
  assert(serverSource.includes('message: "Core update applied. Lumi will restart in a few seconds."'));
  assert(serverSource.includes('`Core version ${status.core.safe_target_version} is available.`'));
  assert(serverSource.includes('`${plugin.name} version ${plugin.safe_target_version} is available.`'));
  assert(updates.includes("data-update-check-form"));
  assert(updates.includes("data-update-inline-result"));
  assert(updates.includes('class="lumi-expandable-settings update-version-picker"'));
  assert(updateSource.includes("The update action failed. Your current page is unchanged."));
  assert(!updates.includes("<script>"), "Updates must use lifecycle-owned client behavior instead of an inline script");
  assert(updateSource.includes('LumiPage.register("update-management"'));
  assert(updateSource.includes("LumiConfirm?.destructiveToken"), "Destructive update actions must attach a timed confirmation token");
  assert(!updateSource.includes("window.location.reload"), "Update recovery must reconnect without reloading the document");
  assert(!updates.includes("reinstall current"));
  assert(!updates.includes(" · core <%= release.core_version %>"));
}

function verifyHomepageEmbeds() {
  const server = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
  const builder = fs.readFileSync(path.join(root, "src", "web", "public", "homepage-builder.js"), "utf8");
  const home = fs.readFileSync(path.join(root, "src", "web", "views", "home.ejs"), "utf8");
  const youtube = fs.readFileSync(path.join(root, "src", "services", "youtube.js"), "utf8");

  assert(server.includes("function twitchParentDomains"));
  assert(server.includes("function twitchChatEmbedUrl"));
  assert(server.includes("function youtubeChatEmbedUrl"));
  assert(server.includes('item.availability_mode === "live_only" && item.live_now !== true'));
  assert(builder.includes('twitch_layout: "stream_only"'));
  assert(builder.includes('youtube_show_chat: false'));
  assert(builder.includes('additional_parent_domains: ""'));
  assert(home.includes("homepageHero.secondary_embed_url"));
  assert(home.includes("homepage-hero-embed-grid"));
  assert(youtube.includes("liveStateUpdatedAt"));
  assert(youtube.includes("activeVideoId"));
}

function verifyCustomPlaceholderSettings() {
  const view = fs.readFileSync(path.join(root, "src", "web", "views", "admin-settings.ejs"), "utf8");
  const client = fs.readFileSync(path.join(root, "src", "web", "public", "custom-placeholders.js"), "utf8");
  const server = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
  assert(view.includes("Reusable text values"));
  assert(view.includes("custom_placeholder_visibility"));
  assert(view.includes("/custom-placeholders.js"));
  assert(client.includes("data-custom-placeholder-row"));
  assert(server.includes("validateCustomPlaceholders(customPlaceholdersFromBody(req.body))"));
  assert(server.includes('setSetting("custom_placeholders"'));
}

const viewCount = verifyViews();
verifyThemeService();
verifyUpdateCachePreserved();
verifySharedUpdateActions();
verifyHomepageEmbeds();
verifyCustomPlaceholderSettings();
console.log(`WebUI verification passed: ${viewCount} EJS views, theme CRUD, and update-cache preservation.`);
