import assert from "node:assert/strict";
import { rmSync } from "node:fs";
import { WebSocket } from "ws";
import { createChatServer } from "../server.js";

rmSync(new URL("../data/db.json", import.meta.url), { force: true });

const instance = await createChatServer({ host: "127.0.0.1", port: 0 });
const base = `http://127.0.0.1:${instance.port}`;

const post = async (path, body) => {
  const response = await fetch(base + path, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  return { status: response.status, data: await response.json() };
};

const first = await post("/api/register", { username: "amirali", password: "1234", name: "امیرعلی" });
assert.equal(first.status, 201, "register should succeed");

const duplicate = await post("/api/register", { username: "amirali", password: "1234" });
assert.equal(duplicate.status, 409, "duplicate username should be rejected");

const second = await post("/api/register", { username: "yohanna", password: "1234", name: "یوحنا" });
assert.equal(second.status, 201);

const badLogin = await post("/api/login", { username: "amirali", password: "wrong" });
assert.equal(badLogin.status, 401, "wrong password should fail");

const login = await post("/api/login", { username: "amirali", password: "1234" });
assert.equal(login.status, 200, "login should succeed");

const sessionResponse = await fetch(`${base}/api/session`, {
  headers: { Authorization: `Bearer ${login.data.token}` },
});
assert.equal(sessionResponse.status, 200);
const sessionData = await sessionResponse.json();
assert.equal(sessionData.users.length, 2, "directory should list every registered account");

const connect = (token) =>
  new Promise((resolve) => {
    const socket = new WebSocket(`ws://127.0.0.1:${instance.port}/ws?token=${token}`);
    const inbox = [];
    socket.on("message", (raw) => inbox.push(JSON.parse(raw.toString())));
    socket.on("open", () => resolve({ socket, inbox }));
  });

const wait = (predicate, label) =>
  new Promise((resolve, reject) => {
    const started = Date.now();
    const timer = setInterval(() => {
      const found = predicate();
      if (found) {
        clearInterval(timer);
        resolve(found);
      } else if (Date.now() - started > 3000) {
        clearInterval(timer);
        reject(new Error(`timeout: ${label}`));
      }
    }, 25);
  });

const alice = await connect(login.data.token);
const bob = await connect(second.data.token);

const aliceInit = await wait(() => alice.inbox.find((m) => m.type === "init"), "alice init");
assert.equal(aliceInit.me.username, "amirali");

await wait(
  () => alice.inbox.some((m) => m.type === "directory" && m.users.some((u) => u.username === "yohanna" && u.online)),
  "presence broadcast",
);

alice.socket.send(
  JSON.stringify({
    type: "message",
    to: second.data.user.id,
    message: { id: "test-1", text: "سلام یوحنا", time: "اکنون" },
  }),
);

const delivered = await wait(
  () => bob.inbox.find((m) => m.type === "message" && m.message.id === "test-1"),
  "message delivery",
);
assert.equal(delivered.message.text, "سلام یوحنا");
assert.equal(delivered.message.from, login.data.user.id);

bob.socket.send(JSON.stringify({ type: "typing", to: login.data.user.id, active: true }));
await wait(() => alice.inbox.find((m) => m.type === "typing" && m.active), "typing relay");

bob.socket.send(JSON.stringify({ type: "reaction", messageId: "test-1", reaction: "💙" }));
await wait(() => alice.inbox.find((m) => m.type === "reaction" && m.reaction === "💙"), "reaction relay");

alice.socket.send(JSON.stringify({ type: "clear", to: second.data.user.id }));
await wait(() => bob.inbox.find((m) => m.type === "clear"), "clear relay");

const stranger = new WebSocket(`ws://127.0.0.1:${instance.port}/ws?token=invalid`);
const closeCode = await new Promise((resolve) => stranger.on("close", resolve));
assert.equal(closeCode, 1008, "invalid token should be rejected");

alice.socket.close();
bob.socket.close();
await instance.close();
console.log("همه تست‌های سرور با موفقیت اجرا شد.");
