import z from "zod/v4";
import { env } from "../env.js";
import { HOURS, MINUTES, SECONDS } from "../utils.js";

const API_ROOT = "https://api.fishfish.gg/v1";

const zDomainCategory = z.literal(["safe", "malware", "phishing"]);

const zDomain = z.object({
  name: z.string(),
  category: zDomainCategory,
  description: z.string(),
  added: z.number(),
  checked: z.number(),
});
export type FishFishDomain = z.output<typeof zDomain>;

const FULL_REFRESH_INTERVAL = 6 * HOURS;
const domains = new Map<string, FishFishDomain>();

let sessionTokenPromise: Promise<string> | null = null;

const WS_RECONNECT_DELAY = 30 * SECONDS;
let updatesWs: WebSocket | null = null;

export class FishFishError extends Error {}

const zTokenResponse = z.object({
  expires: z.number(),
  token: z.string(),
});

async function getSessionToken(): Promise<string> {
  if (sessionTokenPromise) {
    return sessionTokenPromise;
  }

  const apiKey = env.FISHFISH_API_KEY;
  if (!apiKey) {
    throw new FishFishError("FISHFISH_API_KEY is missing");
  }

  sessionTokenPromise = (async () => {
    const response = await fetch(`${API_ROOT}/users/@me/tokens`, {
      method: "POST",
      headers: {
        Authorization: apiKey,
        "Content-Type": "application/json",
      },
    });

    if (!response.ok) {
      throw new FishFishError(`Failed to get session token: ${response.status} ${response.statusText}`);
    }

    const parseResult = zTokenResponse.safeParse(await response.json());
    if (!parseResult.success) {
      throw new FishFishError(`Parse error when fetching session token: ${parseResult.error.message}`);
    }

    const timeUntilExpiry = Date.now() - parseResult.data.expires * 1000;
    setTimeout(() => {
      sessionTokenPromise = null;
    }, timeUntilExpiry - 1 * MINUTES); // Subtract a minute to ensure we refresh before expiry

    return parseResult.data.token;
  })();
  sessionTokenPromise.catch((err) => {
    sessionTokenPromise = null;
    throw err;
  });

  return sessionTokenPromise;
}

async function fishFishApiCall(method: string, path: string, query: Record<string, string> = {}): Promise<unknown> {
  const sessionToken = await getSessionToken();
  const queryParams = new URLSearchParams(query);
  const response = await fetch(`https://api.fishfish.gg/v1/${path}?${queryParams}`, {
    method,
    headers: {
      Authorization: sessionToken,
      "Content-Type": "application/json",
    },
  });

  if (!response.ok) {
    throw new FishFishError(`FishFish API call failed: ${response.status} ${response.statusText}`);
  }

  return response.json();
}

async function subscribeToFishFishUpdates(): Promise<void> {
  if (updatesWs) {
    return;
  }
  const sessionToken = await getSessionToken();
  console.log("[FISHFISH] Connecting to WebSocket for real-time updates");
  updatesWs = new WebSocket("wss://api.fishfish.gg/v1/stream", {
    headers: {
      Authorization: sessionToken,
    },
  });
  updatesWs.addEventListener("open", () => {
    console.log("[FISHFISH] WebSocket connection established");
  });
  updatesWs.addEventListener("message", (event) => {
    console.log("[FISHFISH] ws update:", event.data);
  });
  updatesWs.addEventListener("error", (error) => {
    console.error(`[FISHFISH] WebSocket error: ${error.message}`);
  });
  updatesWs.addEventListener("close", () => {
    console.log("[FISHFISH] WebSocket connection closed, reconnecting after delay");
    updatesWs = null;
    setTimeout(() => {
      subscribeToFishFishUpdates();
    }, WS_RECONNECT_DELAY);
  });
}

async function refreshFishFishDomains() {
  const rawData = await fishFishApiCall("GET", "domains", { full: "true" });
  const parseResult = z.array(zDomain).safeParse(rawData);
  if (!parseResult.success) {
    throw new FishFishError(`Parse error when refreshing domains: ${parseResult.error.message}`);
  }

  domains.clear();
  for (const domain of parseResult.data) {
    domains.set(domain.name, domain);
  }

  domains.set("malware-link.test.zeppelin.gg", {
    name: "malware-link.test.zeppelin.gg",
    category: "malware",
    description: "",
    added: Date.now(),
    checked: Date.now(),
  });
  domains.set("phishing-link.test.zeppelin.gg", {
    name: "phishing-link.test.zeppelin.gg",
    category: "phishing",
    description: "",
    added: Date.now(),
    checked: Date.now(),
  });
  domains.set("safe-link.test.zeppelin.gg", {
    name: "safe-link.test.zeppelin.gg",
    category: "safe",
    description: "",
    added: Date.now(),
    checked: Date.now(),
  });

  console.log("[FISHFISH] Refreshed FishFish domains, total count:", domains.size);
}

export async function initFishFish() {
  if (!env.FISHFISH_API_KEY) {
    console.warn("[FISHFISH] FISHFISH_API_KEY is not set, FishFish functionality will be disabled.");
    return;
  }

  await refreshFishFishDomains();
  void subscribeToFishFishUpdates();
  setInterval(() => refreshFishFishDomains(), FULL_REFRESH_INTERVAL);
}

export function getFishFishDomain(domain: string): FishFishDomain | undefined {
  return domains.get(domain.toLowerCase());
}
