import { test, type TestContext } from "node:test";
import assert from "node:assert/strict";
import { createApp } from "../server.ts";
import { Store } from "../backend/store.ts";
import { Monitor } from "../backend/monitor.ts";
import { Mailer } from "../backend/mail.ts";
import { hashPassword } from "../backend/auth.ts";
import { validateSite } from "../backend/validation.ts";
import type { CheckResult } from "../src/types.ts";
async function fixture(t: TestContext, send?: (log: any) => Promise<void>) {
  const store = new Store(":memory:");
  store.db
    .prepare("INSERT INTO admins VALUES(?,?)")
    .run("testadmin", await hashPassword("TestOnlyPassword123!"));
  const mailer = new Mailer(store, send);
  if (!send) mailer.configured = false;
  const monitor = new Monitor(store, mailer, 2, async (site) => ({
    url: site.url,
    hostname: new URL(site.url).hostname,
    status: "online",
    statusCode: 200,
    statusText: "HTTP 200",
    responseTimeMs: 40,
    isWordPress: true,
    restApiStatus: "healthy",
    ssl: { valid: true, daysRemaining: 40 },
    checkedAt: new Date().toISOString(),
  }));
  const app = createApp(store, monitor);
  const server = app.listen(0, "127.0.0.1");
  await new Promise<void>((resolve) => server.once("listening", resolve));
  const address = server.address() as { port: number };
  const origin = `http://127.0.0.1:${address.port}`;
  let cookie = "",
    csrf = "";
  async function request(
    url: string,
    method = "GET",
    body?: unknown,
    headers: Record<string, string> = {},
  ) {
    const response = await fetch(origin + url, {
      method,
      headers: {
        Origin: origin,
        "X-WP-Pulse": "1",
        "Content-Type": "application/json",
        Cookie: cookie,
        "X-CSRF-Token": csrf,
        ...headers,
      },
      body: body === undefined ? undefined : JSON.stringify(body),
    });
    const data = await response.json();
    return { response, data };
  }
  async function login() {
    const r = await request("/api/auth/login", "POST", {
      username: "testadmin",
      password: "TestOnlyPassword123!",
    });
    assert.equal(r.response.status, 200);
    cookie = r.response.headers.get("set-cookie")!.split(";")[0];
    csrf = r.data.csrf;
    return r;
  }
  t.after(async () => {
    monitor.stop();
    while (monitor.active.size || mailer.busy)
      await new Promise<void>((r) => setImmediate(r));
    await new Promise<void>((r) => server.close(() => r()));
    store.close();
  });
  return {
    store,
    monitor,
    mailer,
    request,
    login,
    origin,
    get cookie() {
      return cookie;
    },
    get csrf() {
      return csrf;
    },
  };
}
test("API requires login, same origin and CSRF, and session cookie is HttpOnly", async (t) => {
  const f = await fixture(t);
  assert.equal((await f.request("/api/state")).response.status, 401);
  assert.equal(
    (
      await f.request(
        "/api/auth/login",
        "POST",
        { username: "testadmin", password: "TestOnlyPassword123!" },
        { Origin: "https://evil.example" },
      )
    ).response.status,
    403,
  );
  assert.equal(
    (
      await f.request("/api/auth/login", "POST", {
        username: "testadmin",
        password: "wrong",
      })
    ).response.status,
    401,
  );
  const login = await f.login();
  assert.match(login.response.headers.get("set-cookie")!, /HttpOnly/);
  assert.match(login.response.headers.get("set-cookie")!, /SameSite=Strict/);
  assert.equal((await f.request("/api/state")).response.status, 200);
  assert.equal(
    (
      await f.request("/api/settings", "PUT", f.store.settings(), {
        "X-CSRF-Token": "",
      })
    ).response.status,
    403,
  );
  assert.equal(
    (await f.request("/api/settings", "PUT", f.store.settings())).response
      .status,
    200,
  );
  assert.equal(
    (
      await f.request("/api/send-alert-email", "POST", {
        recipient: "attacker@example.com",
      })
    ).response.status,
    404,
  );
});
test("API rejects internal URLs and malformed fields; imports normalize and deduplicate", async (t) => {
  const f = await fixture(t);
  await f.login();
  const base = {
    name: "Client",
    url: "http://127.0.0.1",
    clientName: "Owner",
    category: "corporate",
    packageType: "care_plan",
  };
  assert.equal(
    (await f.request("/api/sites", "POST", base)).response.status,
    400,
  );
  assert.equal(
    (
      await f.request("/api/sites", "POST", {
        ...base,
        url: "https://8.8.8.8/",
        clientEmail: "invalid",
      })
    ).response.status,
    400,
  );
  const first = await f.request("/api/sites", "POST", {
    ...base,
    url: "https://8.8.8.8/",
  });
  assert.equal(first.response.status, 201);
  assert.ok(first.data.id);
  assert.equal(
    (
      await f.request("/api/sites", "POST", {
        ...base,
        url: "https://8.8.8.8/",
      })
    ).response.status,
    409,
  );
  const imported = await f.request("/api/sites/import", "POST", {
    sites: [
      { ...base, url: "https://8.8.8.8/" },
      { ...base, url: "https://1.1.1.1/" },
    ],
  });
  assert.equal(imported.data.added, 1);
  assert.equal(imported.data.duplicates, 1);
  assert.equal(
    (await f.request("/api/check", "POST", { siteIds: [first.data.id] }))
      .response.status,
    202,
  );
  const data = (await f.request("/api/state")).data;
  assert.equal(data.sites.length, 2);
  assert.ok(data.sites.every((s: any) => s.lastResult?.url === s.url));
  assert.equal(
    (await f.request(`/api/sites/${first.data.id}/history`)).data[0].status,
    "online",
  );
});
test("SMTP absent reports disabled and recipient cannot be overridden by request", async (t) => {
  const f = await fixture(t);
  await f.login();
  const r = await f.request("/api/email", "POST", {
    mode: "test",
    recipient: "attacker@example.com",
  });
  assert.equal(r.data.success, false);
  assert.equal(r.data.status, "disabled");
  assert.equal(r.data.log.recipient, "admin@alfatihstudio.com");
  assert.equal(r.data.log.attempts, 0);
});
test("summary is one message and exports omit hashes and sessions", async (t) => {
  const delivered: any[] = [];
  const f = await fixture(t, async (log) => {
    delivered.push(log);
  });
  await f.login();
  for (const url of ["https://8.8.8.8/", "https://1.1.1.1/"]) {
    const site = f.store.saveSite(validateSite({ name: "Client", url }));
    const r: CheckResult = {
      url,
      hostname: new URL(url).hostname,
      status: "down",
      statusCode: 500,
      statusText: "HTTP 500",
      responseTimeMs: 100,
      isWordPress: false,
      wpErrorType: "server_error",
      wpErrorMessage: "Server error",
      restApiStatus: "unknown",
      ssl: { valid: true },
      checkedAt: new Date().toISOString(),
    };
    f.store.record(site, r);
    f.store.record(site, r);
  }
  const summary = await f.request("/api/email", "POST", { mode: "summary" });
  assert.equal(summary.data.status, "sent");
  assert.equal(delivered.length, 1);
  assert.match(delivered[0].errorMessage, /8\.8\.8\.8/);
  assert.match(delivered[0].errorMessage, /1\.1\.1\.1/);
  const exported = await f.request("/api/export");
  assert.equal(exported.data.sites.length, 2);
  assert.equal("admins" in exported.data, false);
  assert.equal("sessions" in exported.data, false);
  const raw = JSON.stringify(exported.data);
  assert.equal(raw.includes("scrypt:"), false);
  assert.equal(raw.includes(f.csrf), false);
});
test("password change revokes sessions and logout makes subsequent API requests unauthorized", async (t) => {
  const f = await fixture(t);
  await f.login();
  assert.equal(
    (
      await f.request("/api/auth/password", "POST", {
        currentPassword: "wrong",
        newPassword: "NewTestPassword123!",
      })
    ).response.status,
    403,
  );
  assert.equal(
    (
      await f.request("/api/auth/password", "POST", {
        currentPassword: "TestOnlyPassword123!",
        newPassword: "NewTestPassword123!",
      })
    ).response.status,
    200,
  );
  assert.equal((await f.request("/api/state")).response.status, 401);
  const next = await f.request("/api/auth/login", "POST", {
    username: "testadmin",
    password: "NewTestPassword123!",
  });
  assert.equal(next.response.status, 200);
});
test("login attempts are rate-limited", async (t) => {
  const f = await fixture(t);
  for (let i = 0; i < 8; i++)
    assert.equal(
      (
        await f.request("/api/auth/login", "POST", {
          username: "testadmin",
          password: "wrong",
        })
      ).response.status,
      401,
    );
  const r = await f.request("/api/auth/login", "POST", {
    username: "testadmin",
    password: "wrong",
  });
  assert.equal(r.response.status, 429);
  assert.ok(r.response.headers.get("retry-after"));
});
