import { SHA512_256, type ServerWebSocket } from "bun";
import { SnowflakeGenerator } from "./snowflake.ts";

function getContentType(path: string): string {
  if (path.endsWith(".html")) return "text/html";
  if (path.endsWith(".css")) return "text/css";
  if (path.endsWith(".js")) return "application/javascript";
  if (path.endsWith(".json")) return "application/json";
  if (path.endsWith(".png")) return "image/png";
  if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg";
  if (path.endsWith(".svg")) return "image/svg+xml";
  if (path.endsWith(".ico")) return "image/x-icon";
  return "application/octet-stream";
}

enum OpcodesClientbound {
  authorizeLogin,
  initMessages,
  updateMessages,
  registerSuccess,
  error,
  users,
  keepAlive = 9,
}

enum OpcodesServerbound {
  identify,
  sendMessage,
  register,
  getUsers,
  keepAlive = 9,
}

interface user {
  name: string;
  username: string;
  pfp: string;
  id: number;
  token: string;
  password: string;
  dev: boolean;
}

interface message {
  id: string;
  author: user;
  content: string;
  timestamp: number;
  editedTimestamp?: number;
  replyTo?: string;
  hasSent: true; // client sends false but server should always say true
}

const messages: message[] = await Bun.file("./db/messages.json").json();

const users: user[] = await Bun.file("./db/users.json").json();

const generator = new SnowflakeGenerator();

function sendError(ws: ServerWebSocket, reason: string) {
  ws.send(
    JSON.stringify({
      op: OpcodesClientbound.error,
      data: {
        reason,
      },
    })
  );
  ws.close();
}

const server = Bun.serve({
  port: 80,
  websocket: {
    open(ws) {
      console.log("connected to ws");
      ws.subscribe("messages");
      // @ts-expect-error
      ws.interval = setInterval(() => {
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(
            JSON.stringify({
              op: OpcodesClientbound.keepAlive,
              data: null,
            })
          );
          console.log("sending keeaplivve");
        } else {
          // @ts-expect-error
          clearInterval(ws.interval);
        }
        // @ts-expect-error guhh
        clearTimeout(ws.pingTimeout);
        // @ts-expect-error guhh
        ws.pingTimeout = setTimeout(() => {
          ws.close();
        }, 30_000);

      }, 60_000);
      // @ts-expect-error guhh
      ws.pingTimeout = null;
    },
    message(ws, msg) {
      const obj = JSON.parse(msg.toString());
      let user: string | any[];
      switch (obj.op) {
        case OpcodesServerbound.identify:
          user = users.filter(user => user.token === obj.data.token);
          if (!(user && user.length !== 0)) {
            user = users.filter(
              (user) =>
                user.password ===
                new SHA512_256().update(obj.data.password ?? "").digest("base64")
            );
          }
          if (user && user.length !== 0) {
            ws.send(
              JSON.stringify({
                op: OpcodesClientbound.authorizeLogin,
                data: user[0],
              })
            );
            ws.send(
              JSON.stringify({
                op: OpcodesClientbound.initMessages,
                data: messages,
              })
            );
          } else {
            sendError(ws, "Not a real user");
          }
          break;
        case OpcodesServerbound.sendMessage:
          user = users.filter((user) => user.token === obj.data.token) as user[];
          if (user && user.length !== 0) {
            obj.op = OpcodesClientbound.updateMessages;
            obj.data.id = generator.generateSnowflake();
            obj.data.author = JSON.parse(JSON.stringify(user[0]));
            obj.data.author.password = null;
            obj.data.author.token = null;
            obj.data.hasSent = true;
            obj.data.token = null;
            obj.data.author.dev = user[0].dev;
            ws.publish("messages", JSON.stringify(obj));
            ws.send(JSON.stringify(obj));
            messages.push(obj.data);
            Bun.write("./db/messages.json", JSON.stringify(messages));
          } else {
            sendError(ws, "Not a real user");
          }
          break;
        case OpcodesServerbound.getUsers:
          let mmiow = JSON.parse(JSON.stringify(users)) as user[];
          mmiow.forEach(user => {
            // @ts-expect-error shush
            user.token = null;
            // @ts-expect-error shush
            user.password = null;
          })
          ws.send(JSON.stringify({
            op: OpcodesClientbound.users,
            data: mmiow
          }))
          break;
        case OpcodesServerbound.keepAlive:
          // @ts-expect-error guhh
          clearTimeout(ws.pingTimeout);
          // @ts-expect-error guhh
          ws.pingTimeout = setTimeout(() => {
            ws.close();
          }, 90_000);
          break;
        default:
          break;
      }
    },
    close(ws) {
      console.log("client disconnected");
    },
  },
  async fetch(req, server) {
    const url = new URL(req.url);
    let path = url.pathname;

    if (path === "/") {
      path = "/index.html";
    }

    if (url.pathname === "/ws") {
      const upgraded = server.upgrade(req, {
        // @ts-expect-error guhh whqt
        data: {
          name: new URL(req.url).searchParams.get("name"),
        },
      });
      if (!upgraded) {
        return new Response("Upgrade failed", { status: 400 });
      }
      return;
    }

    path = path.replace(/\.\./g, "");

    try {
      const file = Bun.file(`./dist${path}`);
      if (!(await file.exists())) {
        return new Response(
          "<center><h1>404 Not Found</h1><hr><p>Bun</p></center>",
          { status: 404, headers: { "Content-Type": "text/html" } }
        );
      }

      return new Response(file, {
        headers: {
          "Content-Type": getContentType(path),
        },
      });
    } catch (err) {
      console.error("Error serving file:", err);
      return new Response(
        "<center><h1>500 Internal Server Error</h1><hr><p>Bun</p></center>",
        { status: 500, headers: { "Content-Type": "text/html" } }
      );
    }
  },
});

console.log(`Server running at http://localhost:${server.port}`);
