import { useEffect, useRef, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import type { JoinResponse, SessionPreview, SessionSnapshot } from "../shared/types";
import { getApiBaseUrl, getWsUrl } from "../config/api";
import { tStatic } from "../i18n";
import { registerForPushNotificationsAsync } from "../notifications";

const STORAGE_KEY = "negopoly:session";

type StoredSession = {
  sessionId: string;
  sessionCode: string;
  playerId: string;
};

async function readStoredSession(): Promise<StoredSession | null> {
  try {
    const raw = await AsyncStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    return JSON.parse(raw) as StoredSession;
  } catch {
    return null;
  }
}

async function writeStoredSession(session: StoredSession) {
  await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(session));
}

async function clearStoredSession() {
  await AsyncStorage.removeItem(STORAGE_KEY);
}

export function useSessionManager() {
  const [sessionId, setSessionId] = useState("");
  const [sessionCode, setSessionCode] = useState("");
  const [playerId, setPlayerId] = useState("");
  const [session, setSession] = useState<SessionSnapshot | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [connectionState, setConnectionState] = useState<
    "idle" | "connecting" | "open" | "error"
  >("idle");
  const [tick, setTick] = useState(0);
  const [pushToken, setPushToken] = useState<{
    token: string;
    platform: "ios" | "android";
  } | null>(null);

  const wsRef = useRef<WebSocket | null>(null);
  const lastPushRegistrationRef = useRef<string | null>(null);

  useEffect(() => {
    let mounted = true;
    readStoredSession().then((stored) => {
      if (!mounted || !stored) return;
      setSessionId(stored.sessionId);
      setSessionCode(stored.sessionCode);
      setPlayerId(stored.playerId);
    });
    return () => {
      mounted = false;
    };
  }, []);

  useEffect(() => {
    let mounted = true;
    registerForPushNotificationsAsync().then((token) => {
      if (!mounted) return;
      setPushToken(token);
    });
    return () => {
      mounted = false;
    };
  }, []);

  useEffect(() => {
    const timer = setInterval(() => setTick((value) => value + 1), 1000);
    return () => clearInterval(timer);
  }, []);

  useEffect(() => {
    if (!pushToken || !sessionId || !playerId) return;
    void registerPushTokenFor(sessionId, playerId);
  }, [pushToken, sessionId, playerId]);

  async function registerPushTokenFor(targetSessionId: string, targetPlayerId: string) {
    if (!pushToken) return;
    const signature = `${targetSessionId}:${targetPlayerId}:${pushToken.platform}:${pushToken.token}`;
    if (lastPushRegistrationRef.current === signature) return;
    lastPushRegistrationRef.current = signature;
    try {
      await fetch(`${getApiBaseUrl()}/api/push/register`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          sessionId: targetSessionId,
          playerId: targetPlayerId,
          token: pushToken.token,
          platform: pushToken.platform,
        }),
      });
    } catch {
      // Ignore push registration failures.
    }
  }

  useEffect(() => {
    if (!sessionId || !playerId) {
      setConnectionState("idle");
      setSession(null);
      return;
    }

    setConnectionState("connecting");
    const ws = new WebSocket(getWsUrl(sessionId, playerId));
    wsRef.current = ws;

    ws.onopen = () => setConnectionState("open");
    ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        if (message.type === "state") {
          setSession(message.session as SessionSnapshot);
        }
        if (message.type === "error") {
          setError(message.message);
        }
        if (message.type === "takeover_approved") {
          const assignedId = message.assignedPlayerId as string;
          setPlayerId(assignedId);
          if (sessionId && sessionCode) {
            writeStoredSession({
              sessionId,
              sessionCode,
              playerId: assignedId,
            });
          }
        }
      } catch {
        setError(tStatic("error.parseResponse"));
      }
    };

    ws.onerror = () => setConnectionState("error");
    ws.onclose = (event) => {
      setConnectionState("error");
      const reason = typeof event?.reason === "string" ? event.reason : "";
      if (event?.code === 1008 && /session not found|player not found/i.test(reason)) {
        resetSession();
      }
    };

    const pingTimer = setInterval(() => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: "ping", sessionId, playerId }));
      }
    }, 15000);

    return () => {
      clearInterval(pingTimer);
      ws.close();
    };
  }, [sessionId, playerId]);

  async function requestTakeover(
    dummyId: string,
    overrideSessionId?: string,
    overridePlayerId?: string,
  ): Promise<string | null> {
    const targetSessionId = overrideSessionId ?? sessionId;
    const targetPlayerId = overridePlayerId ?? playerId;
    if (!targetSessionId || !targetPlayerId) {
      const message = tStatic("entry.alert.takeoverFailed");
      setError(message);
      return message;
    }
    const payload = {
      type: "takeover_request",
      sessionId: targetSessionId,
      playerId: targetPlayerId,
      dummyId,
    };
    if (
      wsRef.current &&
      wsRef.current.readyState === WebSocket.OPEN &&
      targetSessionId === sessionId &&
      targetPlayerId === playerId
    ) {
      wsRef.current.send(JSON.stringify(payload));
      return null;
    }
    try {
      const response = await fetch(
        `${getApiBaseUrl()}/api/session/${targetSessionId}/takeover`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ playerId: targetPlayerId, dummyId }),
        },
      );
      if (!response.ok) {
        const data = (await response.json()) as { message?: string };
        const message = data.message ?? tStatic("entry.alert.takeoverFailed");
        setError(message);
        return message;
      }
      return null;
    } catch {
      const message = tStatic("error.connectionNotReady");
      setError(message);
      return message;
    }
  }

  async function createSession(bankerName: string) {
    setError(null);
    setSession(null);
    const response = await fetch(`${getApiBaseUrl()}/api/session`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ bankerName }),
    });
    if (!response.ok) {
      setError(tStatic("error.createSession"));
      return null;
    }
    const data = (await response.json()) as JoinResponse;
    setSessionId(data.sessionId);
    setSessionCode(data.sessionCode);
    setPlayerId(data.playerId);
    await writeStoredSession({
      sessionId: data.sessionId,
      sessionCode: data.sessionCode,
      playerId: data.playerId,
    });
    return data;
  }

  async function joinSession(code: string, name: string) {
    setError(null);
    setSession(null);
    if (!code) {
      setError(tStatic("entry.alert.enterCode"));
      return null;
    }
    const storedNow = await readStoredSession();
    const reusePlayerId =
      storedNow && (storedNow.sessionCode === code || storedNow.sessionId === code)
        ? storedNow.playerId
        : undefined;

    const response = await fetch(`${getApiBaseUrl()}/api/session/${code}/join`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ name, playerId: reusePlayerId }),
    });
    if (!response.ok) {
      setError(tStatic("error.joinSession"));
      return null;
    }
    const data = (await response.json()) as JoinResponse;
    setSessionId(data.sessionId);
    setSessionCode(data.sessionCode);
    setPlayerId(data.playerId);
    await writeStoredSession({
      sessionId: data.sessionId,
      sessionCode: data.sessionCode,
      playerId: data.playerId,
    });
    return data;
  }

  async function requestTakeoverToken(
    code: string,
    dummyId: string,
    name: string,
  ): Promise<string | null> {
    setError(null);
    if (!code || !dummyId) {
      setError(tStatic("entry.alert.selectDummy"));
      return null;
    }
    try {
      const response = await fetch(
        `${getApiBaseUrl()}/api/session/${code}/takeover-request`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ dummyId, name }),
        },
      );
      if (!response.ok) {
        const data = (await response.json()) as { message?: string };
        setError(data.message ?? tStatic("entry.alert.takeoverFailed"));
        return null;
      }
      const data = (await response.json()) as { token?: string };
      return data.token ?? null;
    } catch {
      setError(tStatic("error.connectionNotReady"));
      return null;
    }
  }

  async function claimTakeover(code: string, token: string) {
    try {
      const response = await fetch(
        `${getApiBaseUrl()}/api/session/${code}/takeover-claim`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ token }),
        },
      );
      if (response.status === 409) {
        return null;
      }
      if (!response.ok) {
        const data = (await response.json()) as { message?: string };
        setError(data.message ?? tStatic("entry.alert.takeoverFailed"));
        return null;
      }
      const data = (await response.json()) as JoinResponse;
      setSessionId(data.sessionId);
      setSessionCode(data.sessionCode);
      setPlayerId(data.playerId);
      await writeStoredSession({
        sessionId: data.sessionId,
        sessionCode: data.sessionCode,
        playerId: data.playerId,
      });
      await registerPushTokenFor(data.sessionId, data.playerId);
      return data;
    } catch {
      setError(tStatic("error.connectionNotReady"));
      return null;
    }
  }

  async function fetchSessionPreview(code: string): Promise<SessionPreview | null> {
    if (!code) return null;
    const response = await fetch(`${getApiBaseUrl()}/api/session/${code}/info`);
    if (!response.ok) {
      setError(tStatic("error.loadSessionInfo"));
      return null;
    }
    return (await response.json()) as SessionPreview;
  }

  function sendMessage(payload: Record<string, unknown>) {
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
      setError(tStatic("error.connectionNotReady"));
      return;
    }
    wsRef.current.send(JSON.stringify(payload));
  }

  async function resetSession() {
    try {
      await clearStoredSession();
    } catch {
      // Ignore storage errors on reset.
    }
    setSessionId("");
    setSessionCode("");
    setPlayerId("");
    setSession(null);
    setError(null);
    setConnectionState("idle");
  }

  async function leaveSession() {
    const ws = wsRef.current;
    wsRef.current = null;
    if (ws && ws.readyState !== WebSocket.CLOSED) {
      try {
        ws.close();
      } catch {
        // Ignore failures while closing the socket.
      }
    }
    await resetSession();
  }

  const me = session?.players.find((player) => player.id === playerId) ?? null;
  const isBanker = me?.role === "banker";

  return {
    sessionId,
    sessionCode,
    playerId,
    session,
    me,
    isBanker,
    tick,
    error,
    connectionState,
    setError,
    createSession,
    joinSession,
    fetchSessionPreview,
    requestTakeoverToken,
    claimTakeover,
    sendMessage,
    resetSession,
    leaveSession,
    setSessionId,
    setPlayerId,
    setSession,
    requestTakeover,
  };
}
