import React, { useState, useEffect, KeyboardEvent, useCallback } from "react";
import { Input, Paper, Typography } from "@mui/material";

import { Session, Lobby } from "./GlobalContext";
import { UserList } from "./UserList";
import { LobbyChat } from "./LobbyChat";
import BotManager from "./BotManager";
import "./App.css";
import { ws_base, base } from "./Common";
import { Box, Button, Tooltip } from "@mui/material";
import { BrowserRouter as Router, Route, Routes, useParams } from "react-router-dom";
import useWebSocket, { ReadyState } from "react-use-websocket";
import ConnectionStatus from "./ConnectionStatus";
import { sessionsApi, LobbyCreateRequest } from "./api-client";

console.log(`AI Voice Chat Build: ${process.env.REACT_APP_AI_VOICECHAT_BUILD}`);

type LobbyProps = {
  session: Session;
  setSession: React.Dispatch<React.SetStateAction<Session | null>>;
  setError: React.Dispatch<React.SetStateAction<string | null>>;
};

const LobbyView: React.FC<LobbyProps> = (props: LobbyProps) => {
  const { session, setSession, setError } = props;
  const { lobbyName = "default" } = useParams<{ lobbyName: string }>();
  const [lobby, setLobby] = useState<Lobby | null>(null);
  const [editName, setEditName] = useState<string>("");
  const [editPassword, setEditPassword] = useState<string>("");
  const [socketUrl, setSocketUrl] = useState<string | null>(null);
  const [creatingLobby, setCreatingLobby] = useState<boolean>(false);
  const [reconnectAttempt, setReconnectAttempt] = useState<number>(0);
  const [shouldRetryLobby, setShouldRetryLobby] = useState<boolean>(false);

  // Check if lobbyName looks like a lobby ID (32 hex characters) and redirect to default
  useEffect(() => {
    if (lobbyName && /^[a-f0-9]{32}$/i.test(lobbyName)) {
      console.log(`Lobby - Detected lobby ID in URL (${lobbyName}), redirecting to default lobby`);
      window.history.replaceState(null, "", `${base}/lobby/default`);
      window.location.reload(); // Force reload to use the new URL
    }
  }, [lobbyName]);

  const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(socketUrl, {
    onOpen: () => {
      console.log("app - WebSocket connection opened.");
      setReconnectAttempt(0);
    },
    onClose: () => {
      console.log("app - WebSocket connection closed.");
      setReconnectAttempt((prev) => prev + 1);
    },
    onError: (event: Event) => {
      console.error("app - WebSocket error observed:", event);
      // If we get a WebSocket error, it might be due to invalid lobby ID
      // Reset the lobby state to force recreation
      if (lobby) {
        console.log("app - WebSocket error, clearing lobby state to force refresh");
        setLobby(null);
        setSocketUrl(null);
      }
    },
    shouldReconnect: (closeEvent) => {
      // Don't reconnect if the lobby doesn't exist (4xx errors)
      if (closeEvent.code >= 4000 && closeEvent.code < 5000) {
        console.log("app - WebSocket closed with client error, not reconnecting");
        return false;
      }
      return true;
    },
    reconnectInterval: 5000, // Retry every 5 seconds
    onReconnectStop: (numAttempts) => {
      console.log(`Stopped reconnecting after ${numAttempts} attempts`);
    },
    share: true,
  });

  useEffect(() => {
    if (lobby && session) {
      setSocketUrl(`${ws_base}/${lobby.id}/${session.id}`);
    }
  }, [lobby, session]);

  useEffect(() => {
    if (!lastJsonMessage || !session) {
      return;
    }
    const data: any = lastJsonMessage;
    switch (data.type) {
      case "update_name":
        if (data.data && "name" in data.data) {
          console.log(`Lobby - name set to ${data.data.name}`);
          setSession((s) => (s ? { ...s, name: data.data.name } : null));
        }
        break;
      case "error":
        console.error(`Lobby - Server error: ${data.data.error}`);
        setError(data.data.error);

        // If the error is about lobby not found, reset the lobby state
        if (data.data.error && data.data.error.includes("Lobby not found")) {
          console.log("Lobby - Lobby not found error, clearing lobby state");
          setLobby(null);
          setSocketUrl(null);
        }
        break;
      default:
        break;
    }
  }, [lastJsonMessage, session, setError, setSession]);

  useEffect(() => {
    console.log("app - WebSocket connection status: ", readyState);
  }, [readyState]);

  // Retry lobby creation when session is restored after a failure
  useEffect(() => {
    if (session && shouldRetryLobby && !lobby && !creatingLobby) {
      console.log("Lobby - Session restored, retrying lobby creation");
      setShouldRetryLobby(false);
      // The main lobby creation effect will trigger automatically due to session change
    }
  }, [session, shouldRetryLobby, lobby, creatingLobby]);

  useEffect(() => {
    if (!session || !lobbyName || creatingLobby || (lobby && lobby.name === lobbyName)) {
      return;
    }

    // Clear any existing lobby state when switching to a new lobby name
    if (lobby && lobby.name !== lobbyName) {
      console.log(`Lobby - Clearing previous lobby state: ${lobby.name} -> ${lobbyName}`);
      setLobby(null);
      setSocketUrl(null);
    }

    const getLobby = async (lobbyName: string, session: Session) => {
      try {
        const lobbyRequest: LobbyCreateRequest = {
          type: "lobby_create",
          data: {
            name: lobbyName,
            private: false,
          },
        };

        const response = await sessionsApi.createLobby(session.id, lobbyRequest);

        if (response.type !== "lobby_created") {
          console.error(`Lobby - Unexpected response type: ${response.type}`);
          setError(`Unexpected response from server`);
          return;
        }
        const lobby: Lobby = response.data;
        console.log(`Lobby - Joined lobby`, lobby);
        setLobby(lobby);
      } catch (err) {
        const errorMessage = err instanceof Error ? err.message : "Failed to create/join lobby";
        console.error("Lobby creation error:", errorMessage);
        setError(errorMessage);

        // If it's a server error (5xx), mark for retry when session is restored
        if (
          err instanceof Error &&
          (err.message.includes("502") || err.message.includes("503") || err.message.includes("500"))
        ) {
          console.log("Lobby - Server error detected, will retry when session is restored");
          setShouldRetryLobby(true);
        } else {
          console.log("Lobby - Non-retryable error, clearing session");
          setSession(null);
        }
      }
    };

    setCreatingLobby(true);
    getLobby(lobbyName, session).finally(() => {
      setCreatingLobby(false);
    });
  }, [session, lobbyName, lobby, setLobby, setError, creatingLobby]);
  const setName = (name: string) => {
    sendJsonMessage({
      type: "set_name",
      data: { name, password: editPassword ? editPassword : undefined },
    });
  };

  const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {
    if (event.key === "Enter") {
      event.preventDefault();
      const newName = editName.trim();
      if (!newName || session?.name === newName) {
        return;
      }
      setName(newName);
      setEditName("");
    }
  };

  return (
    <Paper className="Lobby" sx={{ p: 2, m: 2, width: "fit-content" }}>
      {readyState !== ReadyState.OPEN || !session ? (
        <ConnectionStatus readyState={readyState} reconnectAttempt={reconnectAttempt} />
      ) : (
        <>
          <Box sx={{ mb: 2, display: "flex", gap: 2, alignItems: "flex-start", flexDirection: "column" }}>
            <Box>
              <Typography variant="h5">AI Voice Chat Lobby: {lobbyName}</Typography>
            </Box>
            <Box>
              {session.name && <Typography>You are logged in as: {session.name}</Typography>}
              {!session.name && (
                <Box sx={{ gap: 1, display: "flex", flexDirection: "column", alignItems: "flex-start" }}>
                  <Typography>Enter your name to join:</Typography>
                  <Typography variant="caption">
                    You can optionally set a password to reserve this name; supply it again to takeover the name from
                    another client.
                  </Typography>
                  <Box sx={{ display: "flex", gap: 1, width: "100%" }}>
                    <Input
                      type="text"
                      value={editName}
                      onChange={(e): void => {
                        setEditName(e.target.value);
                      }}
                      onKeyDown={handleKeyDown}
                      placeholder="Your name"
                    />
                    <Input
                      type="password"
                      value={editPassword}
                      onChange={(e): void => setEditPassword(e.target.value)}
                      placeholder="Optional password"
                    />
                    <Tooltip title="Optional: choose a short password to reserve this name. Keep it secret.">
                      <span />
                    </Tooltip>
                    <Button
                      variant="contained"
                      onClick={() => {
                        setName(editName);
                        setEditName("");
                        setEditPassword("");
                      }}
                      disabled={!editName.trim()}
                    >
                      Join
                    </Button>
                  </Box>
                </Box>
              )}
            </Box>
          </Box>

          {session.name && (
            <>
              {/* {session.lobbies.map((lobby: string) => (
                <Box key={lobby}>
                  <Button variant="contained" disabled={lobby === lobbyName} sx={{ mr: 1, mb: 1 }}>
                    {lobby === lobbyName ? `In Lobby: ${lobby}` : `Join Lobby: ${lobby}`}
                  </Button>
                </Box>
              ))} */}

              <Box sx={{ display: "flex", gap: 2, flexWrap: "wrap" }}>
                {session && socketUrl && lobby && (
                  <UserList socketUrl={socketUrl} session={session} lobbyId={lobby.id} />
                )}
                {session && socketUrl && lobby && (
                  <LobbyChat socketUrl={socketUrl} session={session} lobbyId={lobby.id} />
                )}
                {session && lobby && (
                  <BotManager
                    lobbyId={lobby.id}
                    onBotAdded={(botName) => console.log(`Bot ${botName} added to lobby`)}
                    sx={{ minWidth: "300px" }}
                  />
                )}
              </Box>
            </>
          )}
        </>
      )}
    </Paper>
  );
};

const App = () => {
  const [session, setSession] = useState<Session | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [sessionRetryAttempt, setSessionRetryAttempt] = useState<number>(0);

  useEffect(() => {
    if (error) {
      setTimeout(() => setError(null), 5000);
    }
  }, [error]);

  useEffect(() => {
    if (!session) {
      return;
    }
    console.log(`App - sessionId`, session.id);
  }, [session]);

  const getSession = useCallback(async () => {
    try {
      const session = await sessionsApi.getCurrent();
      setSession(session);
      setSessionRetryAttempt(0);
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : "Unknown error occurred";
      console.error("Failed to get session:", errorMessage);
      setError(errorMessage);

      // Schedule retry after 5 seconds
      setSessionRetryAttempt((prev) => prev + 1);
      setTimeout(() => {
        getSession(); // Retry
      }, 5000);
    }
  }, []);

  useEffect(() => {
    if (session) {
      return;
    }
    getSession();
  }, [session, getSession]);

  return (
    <Box>
      {!session && (
        <ConnectionStatus
          readyState={sessionRetryAttempt > 0 ? ReadyState.CLOSED : ReadyState.CONNECTING}
          reconnectAttempt={sessionRetryAttempt}
        />
      )}
      {session && (
        <Router>
          <Routes>
            <Route element={<LobbyView {...{ setError, session, setSession }} />} path={`${base}/:lobbyName`} />
            <Route element={<LobbyView {...{ setError, session, setSession }} />} path={`${base}`} />
          </Routes>
        </Router>
      )}
      {error && (
        <Paper className="Error" sx={{ p: 2, m: 2, width: "fit-content", backgroundColor: "#ffdddd" }}>
          <Typography color="red">{error}</Typography>
        </Paper>
      )}
    </Box>
  );
};

export default App;
