import {useIngame} from "@/composables/useIngame.ts";
import type {GameInfo, LobbyInfo, TieInfo, TrumpInfo, WonInfo} from "@/types/GameTypes.ts";
import router from "@/router";

const api = "localhost:9000"

// ---------- Types ---------------------------------------------------------

export type ServerMessage<T = any> = {
  id?: string;
  event?: string;
  status?: "success" | "error";
  state?: "Lobby" | "InGame" | "SelectTrump" | "TieBreak" | "FinishedMatch";
  stateData?: GameInfo | LobbyInfo | TieInfo | TrumpInfo | WonInfo;
  data?: T;
  error?: string;
};

export type ClientMessage<T = any> = {
  id: string;
  event: string;
  data: T;
};

export type HandlerFn<T = any> = (data: T) => unknown | Promise<unknown>;

interface PendingEntry {
  resolve: (data: any) => void;
  reject: (err: Error) => void;
  timer: ReturnType<typeof setTimeout>;
}

let ws: WebSocket | null = null;

const pending = new Map<string, PendingEntry>();
const handlers = new Map<string, HandlerFn>();
let uState: ReturnType<typeof useIngame> | null = null;

let heartbeatTimer: ReturnType<typeof setInterval> | null = null;

export function initWebSocket(ingameStore: ReturnType<typeof useIngame>) {
  uState = ingameStore;
}
let defaultHandler: HandlerFn | null = null;

function uuid(): string {
  return crypto.randomUUID();
}

function failAllPending(reason: string) {
  for (const [, entry] of pending.entries()) {
    clearTimeout(entry.timer);
    entry.reject(new Error(reason));
  }
  pending.clear();
}

function startHeartbeat() {
  stopHeartbeat();
  heartbeatTimer = setInterval(() => {
    if (ws && ws.readyState === WebSocket.OPEN) {
      sendEventAndWait("ping", {}, 4000)
        .then(() => console.debug("[WS] Heartbeat OK"))
        .catch((err) => console.warn("[WS] Heartbeat failed:", err.message));
    }
  }, 5000);
}

function stopHeartbeat() {
  if (heartbeatTimer) {
    clearInterval(heartbeatTimer);
    heartbeatTimer = null;
  }
}

function setupSocketHandlers(socket: WebSocket) {
  if (!uState) {
    console.error("[WS] WebSocket module not initialized with Pinia store!");
    return;
  }
  socket.onmessage = async (raw) => {
    console.debug("[WS] MESSAGE:", raw.data);

    let msg: ServerMessage;
    try {
      msg = JSON.parse(raw.data);
    } catch (err) {
      console.warn("[WS] Bad JSON:", raw.data, err);
      return;
    }

    const { id, event, state, stateData, status, data } = msg;

    // RPC response branch
    if (id && status) {
      const entry = pending.get(id);
      if (!entry) return;

      pending.delete(id);
      clearTimeout(entry.timer);

      if (status === "success") {
        entry.resolve(data ?? {});
      } else {
        entry.reject(new Error(msg.error || "Server returned error"));
      }
      return;
    }

    if (state && stateData) {
      console.debug("[WS] State change:", state, stateData);
      if(uState) {
        console.debug("[WS] State change cascade:", state, stateData);
        uState.setIngame(state, stateData);
      }
    }

    // Server event → handler branch
    if (id && event) {
      const handler = handlers.get(event);

      const reply = (status: "success" | "error", error?: string) => {
        if (socket.readyState !== WebSocket.OPEN) return;
        const resp = { id, event, status, error };
        socket.send(JSON.stringify(resp));
      };

      if (!handler) {
        console.warn("[WS] No handler for event:", event);
        if (defaultHandler) {
          try {
            await defaultHandler(data ?? {});
            reply("success");
          } catch (err) {
            reply("error", (err as Error).message);

          }
        } else {
          reply("error", `No handler for '${event}'`);
        }
        return;
      }

      try {
        await handler(data ?? {});
        reply("success");
      } catch (err) {
        reply("error", (err as Error).message);
      }
    }
  };

  socket.onerror = (err) => {
    console.error("[WS] ERROR:", err);
    stopHeartbeat();
    failAllPending("WebSocket error");
  };

  socket.onclose = (ev) => {
    stopHeartbeat();
    failAllPending("WebSocket closed");

    if (ev.wasClean) {
      console.log(`[WS] Closed cleanly: code=${ev.code} reason=${ev.reason}`);
    } else {
      console.warn("[WS] Connection died");
    }

    // You redirect here — if you don’t want auto reconnect, keep as is.
    router.replace("/");
  };
}

export function connectWebSocket(url?: string): Promise<void> {
  if (!url) {
    const loc = window.location;
    const protocol = loc.protocol === "https:" ? "wss:" : "ws:";
    url = `${protocol}//${api}/websocket`;
  }

  if (ws && ws.readyState === WebSocket.OPEN) {
    return Promise.resolve();
  }

  if (ws && ws.readyState === WebSocket.CONNECTING) {
    return new Promise((resolve, reject) => {
      const prevOnOpen = ws!.onopen;
      const prevOnError = ws!.onerror;
      ws!.onopen = (ev) => {
        if (prevOnOpen) prevOnOpen.call(ws!, ev);
        resolve();
      };
      ws!.onerror = (err) => {
        if (prevOnError) prevOnError.call(ws!, err);
        reject(err);
      };
    });
  }

  // New connection
  ws = new WebSocket(url);
  setupSocketHandlers(ws);

  return new Promise((resolve, reject) => {
    ws!.onopen = () => {
      console.log("[WS] Connected");
      startHeartbeat();
      resolve();
    };

    ws!.onerror = (err) => reject(err);
  });
}

export function disconnectWebSocket(code = 1000, reason = "Client disconnect") {
  stopHeartbeat();
  failAllPending("Disconnected");

  if (ws) {
    try {
      ws.close(code, reason);
    } catch {}
    ws = null;
  }
}

export function sendEvent(event: string, data: any) {
  if (!ws || ws.readyState !== WebSocket.OPEN) {
    console.warn("[WS] Send failed: not open");
    return;
  }

  const message: ClientMessage = { id: uuid(), event, data };
  console.debug("[WS] SEND:", message);
  ws.send(JSON.stringify(message));
}

export function sendEventAndWait(
  event: string,
  data: any,
  timeoutMs = 10000
): Promise<any> {
  if (!ws || ws.readyState !== WebSocket.OPEN) {
    return Promise.reject(new Error("WebSocket is not open"));
  }

  const id = uuid();
  const message: ClientMessage = { id, event, data };

  const promise = new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      pending.delete(id);
      reject(new Error(`Timeout waiting for '${event}'`));
    }, timeoutMs);

    pending.set(id, { resolve, reject, timer });
  });

  console.debug("[WS] SEND (await):", message);
  ws.send(JSON.stringify(message));

  return promise;
}

export function onEvent(event: string, handler: HandlerFn) {
  handlers.set(event, handler);
}

export function setDefaultHandler(handler: HandlerFn) {
  defaultHandler = handler;
}

export const isWebSocketConnected = () =>
  !!ws && ws.readyState === WebSocket.OPEN;
