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 "./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";

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 { 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),
    shouldReconnect: (closeEvent) => true, // Will attempt to reconnect on all close events
    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.error}`);
        setError(data.error);
        break;
      default:
        break;
    }
  }, [lastJsonMessage, session, setError, setSession]);

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

  useEffect(() => {
    if (!session || !lobbyName || creatingLobby || (lobby && lobby.name === lobbyName)) {
      return;
    }
    const getLobby = async (lobbyName: string, session: Session) => {
      const res = await fetch(`${base}/api/lobby/${session.id}`, {
        method: "POST",
        cache: "no-cache",
        credentials: "same-origin",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          type: "lobby_create",
          data: {
            name: lobbyName,
            private: false,
          },
        }),
      });

      if (res.status >= 400) {
        const error = `Unable to connect to AI Voice Chat server! Try refreshing your browser in a few seconds.`;
        console.error(error);
        setError(error);
      }

      const data = await res.json();
      if (data.error) {
        console.error(`Lobby - Server error: ${data.error}`);
        setError(data.error);
        return;
      }
      if (data.type !== "lobby_created") {
        console.error(`Lobby - Unexpected response type: ${data.type}`);
        setError(`Unexpected response from server`);
        return;
      }
      const lobby: Lobby = data.data;
      console.log(`Lobby - Joined lobby`, lobby);
      setLobby(lobby);
    };

    setCreatingLobby(true);
    getLobby(lobbyName, session).then(() => {
      setCreatingLobby(false);
    });
  }, [session, lobbyName, setLobby, setError]);

  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 && <UserList socketUrl={socketUrl} session={session} />}
                {session && socketUrl && lobby && (
                  <LobbyChat socketUrl={socketUrl} session={session} lobbyId={lobby.id} />
                )}
              </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 res = await fetch(`${base}/api/session`, {
        method: "GET",
        cache: "no-cache",
        credentials: "same-origin",
        headers: {
          "Content-Type": "application/json",
        },
      });

      if (res.status >= 400) {
        throw new Error(`HTTP ${res.status}: Unable to connect to AI Voice Chat server`);
      }
      
      const data = await res.json();
      if (data.error) {
        throw new Error(`Server error: ${data.error}`);
      }
      
      setSession(data);
      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;
