/**
 * Guards the shipped webview security configuration.
 *
 * `csp` was `null` and the asset protocol was scoped to the whole storage root,
 * which is the directory holding the SQLite database and the encrypted-token
 * fallback file. Both are one-character regressions away and neither is visible
 * in any behavioural test, so they are asserted here instead: the restrictive
 * half of the policy must stay restrictive, and the permissive half must keep
 * the schemes playback actually needs.
 *
 * TRACES: UR-012, UR-071 | DR-198 | UT-193
 */

import { describe, it, expect } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";

const config = JSON.parse(
  readFileSync(resolve(__dirname, "../src-tauri/tauri.conf.json"), "utf-8")
);

const security = config.app.security;

/** Split a CSP string into `directive -> sources`. */
function directives(csp: string): Record<string, string[]> {
  const map: Record<string, string[]> = {};
  for (const part of csp.split(";")) {
    const [name, ...sources] = part.trim().split(/\s+/);
    if (name) map[name] = sources;
  }
  return map;
}

describe("tauri.conf.json CSP", () => {
  it("is set at all — a null CSP hands any injected script the full IPC surface", () => {
    expect(typeof security.csp).toBe("string");
    expect(security.csp.length).toBeGreaterThan(0);
  });

  const csp = directives(security.csp as string);

  it("locks down script execution", () => {
    // Tauri injects a nonce for SvelteKit's inline bootstrap script at build
    // time, so 'self' alone is enough and inline/eval must never be re-added.
    expect(csp["script-src"]).toEqual(["'self'"]);
    expect(csp["object-src"]).toEqual(["'none'"]);
    expect(csp["frame-src"]).toEqual(["'none'"]);
    expect(csp["base-uri"]).toEqual(["'self'"]);
    expect(csp["default-src"]).toEqual(["'self'"]);
  });

  it("keeps the schemes playback and thumbnails depend on", () => {
    // The asset protocol under both names convertFileSrc emits.
    expect(csp["img-src"]).toContain("asset:");
    expect(csp["img-src"]).toContain("http://asset.localhost");
    expect(csp["media-src"]).toContain("asset:");
    // hls.js: MSE object URLs, and its demuxer worker built from a blob.
    expect(csp["media-src"]).toContain("blob:");
    expect(csp["worker-src"]).toContain("blob:");
    // The token-guarded loopback media server (DR-137).
    expect(csp["media-src"]).toContain("http://127.0.0.1:*");
    // Tauri's invoke transport.
    expect(csp["connect-src"]).toContain("ipc:");
    expect(csp["connect-src"]).toContain("http://ipc.localhost");
    // The user's Jellyfin server: an arbitrary run-time origin, http on a LAN.
    for (const directive of ["img-src", "media-src", "connect-src"]) {
      expect(csp[directive]).toContain("http:");
      expect(csp[directive]).toContain("https:");
    }
  });

  it("never widens a data directive into script execution", () => {
    for (const [name, sources] of Object.entries(csp)) {
      if (name === "script-src" || name === "worker-src") {
        expect(sources).not.toContain("'unsafe-eval'");
        expect(sources).not.toContain("'unsafe-inline'");
      }
      // A bare `*` would re-admit every scheme, including file:.
      expect(sources).not.toContain("*");
    }
  });
});

describe("tauri.conf.json asset protocol scope", () => {
  const scope: string[] = security.assetProtocol.scope;

  it("covers only the thumbnail cache, not the storage root", () => {
    expect(scope).toEqual(["$APPDATA/thumbnails/**"]);
    // The database and the encrypted-token fallback live directly in $APPDATA.
    expect(scope).not.toContain("$APPDATA/**");
  });
});
