import { useCallback, useEffect, useRef, useState } from "react";
import * as SecureStore from "expo-secure-store";
import { env } from "@haushaltsApp/env/native";
import { useAuthStore } from "../stores/auth.store";

// Mirroring @haushaltsApp/shared/schemas/shopping.schema types
// (workspace package exports are not resolved by Expo's TS config)
export type ShoppingItem = {
  id: string;
  householdId: string;
  label: string;
  quantity: string | null;
  addedBy: string;
  checkedBy: string | null;
  checkedAt: string | null;
  sortOrder: number;
  createdAt: string;
};

type ShoppingServerEvent =
  | { type: "item:added"; item: ShoppingItem }
  | { type: "item:checked"; itemId: string; checkedBy: string; checkedAt: string }
  | { type: "item:unchecked"; itemId: string }
  | { type: "item:deleted"; itemId: string }
  | { type: "item:cleared" }
  | { type: "sync"; items: ShoppingItem[] };

type ShoppingClientCommand =
  | { type: "item:add"; label: string; quantity?: string }
  | { type: "item:check"; itemId: string }
  | { type: "item:uncheck"; itemId: string }
  | { type: "item:delete"; itemId: string }
  | { type: "item:clear" };

// expoClient plugin stores the session token under "<storagePrefix>.session_token"
const TOKEN_KEY = "haushaltsapp.session_token";
const MAX_BACKOFF_MS = 16_000;

function getWsUrl(serverUrl: string, householdId: string, token: string): string {
  // Convert http(s) to ws(s) for WebSocket connection
  const base = serverUrl.replace(/^http/, "ws");
  return `${base}/api/shopping-lists/ws?householdId=${encodeURIComponent(householdId)}&token=${encodeURIComponent(token)}`;
}

export type ConnectionStatus = "connecting" | "connected" | "offline";

export function useShoppingList() {
  const activeHouseholdId = useAuthStore((s) => s.activeHouseholdId);
  const [items, setItems] = useState<ShoppingItem[]>([]);
  const [status, setStatus] = useState<ConnectionStatus>("connecting");

  const wsRef = useRef<WebSocket | null>(null);
  const backoffRef = useRef(1_000);
  const retryTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const mountedRef = useRef(true);
  const offlineQueueRef = useRef<ShoppingClientCommand[]>([]);

  const send = useCallback((cmd: ShoppingClientCommand) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify(cmd));
    } else {
      offlineQueueRef.current.push(cmd);
    }
  }, []);

  function handleServerEvent(event: ShoppingServerEvent) {
    switch (event.type) {
      case "sync":
        setItems(event.items);
        break;
      case "item:added":
        setItems((prev) => {
          // Replace optimistic placeholder (keyed by label) or append
          const withoutTemp = prev.filter((i) => i.id !== `temp-${event.item.label}`);
          return [...withoutTemp, event.item];
        });
        break;
      case "item:checked":
        setItems((prev) =>
          prev.map((i) =>
            i.id === event.itemId
              ? { ...i, checkedBy: event.checkedBy, checkedAt: event.checkedAt }
              : i,
          ),
        );
        break;
      case "item:unchecked":
        setItems((prev) =>
          prev.map((i) =>
            i.id === event.itemId ? { ...i, checkedBy: null, checkedAt: null } : i,
          ),
        );
        break;
      case "item:deleted":
        setItems((prev) => prev.filter((i) => i.id !== event.itemId));
        break;
      case "item:cleared":
        setItems((prev) => prev.filter((i) => i.checkedBy === null));
        break;
    }
  }

  const connect = useCallback(async () => {
    if (!activeHouseholdId || !mountedRef.current) return;

    const token = (await SecureStore.getItemAsync(TOKEN_KEY)) ?? "";
    if (!token) return;
    const wsUrl = getWsUrl(env.EXPO_PUBLIC_SERVER_URL, activeHouseholdId, token);

    const ws = new WebSocket(wsUrl);
    wsRef.current = ws;
    setStatus("connecting");

    ws.onopen = () => {
      if (!mountedRef.current) {
        ws.close();
        return;
      }
      setStatus("connected");
      backoffRef.current = 1_000;
      // Flush offline queue
      const queue = offlineQueueRef.current.splice(0);
      for (const cmd of queue) ws.send(JSON.stringify(cmd));
    };

    ws.onmessage = (event) => {
      if (!mountedRef.current) return;
      try {
        const serverEvent = JSON.parse(event.data as string) as ShoppingServerEvent;
        handleServerEvent(serverEvent);
      } catch {
        // Ignore malformed messages
      }
    };

    ws.onerror = () => {
      ws.close();
    };

    ws.onclose = () => {
      if (!mountedRef.current) return;
      setStatus("offline");
      wsRef.current = null;
      retryTimeoutRef.current = setTimeout(() => {
        backoffRef.current = Math.min(backoffRef.current * 2, MAX_BACKOFF_MS);
        void connect();
      }, backoffRef.current);
    };
  }, [activeHouseholdId]);

  useEffect(() => {
    mountedRef.current = true;
    void connect();
    return () => {
      mountedRef.current = false;
      if (retryTimeoutRef.current) clearTimeout(retryTimeoutRef.current);
      wsRef.current?.close();
    };
  }, [connect]);

  // ── Mutations (optimistic + WS) ────────────────────────────────────────────

  const addItem = useCallback(
    (label: string, quantity?: string) => {
      const trimmed = label.trim();
      if (!trimmed) return;

      // Optimistic placeholder
      const tempItem: ShoppingItem = {
        id: `temp-${trimmed}`,
        householdId: activeHouseholdId ?? "",
        label: trimmed,
        quantity: quantity ?? null,
        addedBy: "",
        checkedBy: null,
        checkedAt: null,
        sortOrder: 0,
        createdAt: new Date().toISOString(),
      };
      setItems((prev) => [...prev, tempItem]);
      send({ type: "item:add", label: trimmed, quantity });
    },
    [activeHouseholdId, send],
  );

  const toggleItem = useCallback(
    (item: ShoppingItem) => {
      const isChecked = item.checkedBy !== null;
      // Optimistic update
      setItems((prev) =>
        prev.map((i) =>
          i.id === item.id
            ? isChecked
              ? { ...i, checkedBy: null, checkedAt: null }
              : { ...i, checkedBy: "optimistic", checkedAt: new Date().toISOString() }
            : i,
        ),
      );
      send(
        isChecked
          ? { type: "item:uncheck", itemId: item.id }
          : { type: "item:check", itemId: item.id },
      );
    },
    [send],
  );

  const deleteItem = useCallback(
    (itemId: string) => {
      // Optimistic update
      setItems((prev) => prev.filter((i) => i.id !== itemId));
      send({ type: "item:delete", itemId });
    },
    [send],
  );

  const deleteChecked = useCallback(() => {
    // Optimistic update
    setItems((prev) => prev.filter((i) => i.checkedBy === null));
    send({ type: "item:clear" });
  }, [send]);

  return { items, status, addItem, toggleItem, deleteItem, deleteChecked };
}
