import {
    BrowserRouter,
    Navigate,
    Outlet,
    Route,
    Routes,
    useLocation,
} from "react-router-dom"

import { Header } from "./pages/template/Header.tsx"
import "./style/app.css"
import {
    createContext,
    lazy,
    ReactNode,
    Suspense,
    useCallback,
    useContext,
    useEffect,
    useMemo,
    useState,
} from "react"
import { BASE } from "./Constants.ts"
import { Authentication, Fetcher } from "./app/Fetcher.ts"
import { User } from "./model/User.ts"
import { VisualizerPage } from "./pages/VisualizerPage.tsx"

const HomePage = lazy(() => import("./pages/HomePage.tsx"))
const LoginPage = lazy(() => import("./pages/LoginPage.tsx"))
const RegisterPage = lazy(() => import("./pages/RegisterPage.tsx"))
const NotFoundPage = lazy(() => import("./pages/404.tsx"))
const CreateTeamPage = lazy(() => import("./pages/CreateTeamPage.tsx"))
const TeamPanelPage = lazy(() => import("./pages/TeamPanel.tsx"))
const NewTacticPage = lazy(() => import("./pages/NewTacticPage.tsx"))
const Editor = lazy(() => import("./pages/Editor.tsx"))
const Settings = lazy(() => import("./pages/Settings.tsx"))

const TOKEN_REFRESH_INTERVAL_MS = 60 * 1000

export default function App() {
    function suspense(node: ReactNode) {
        return (
            <Suspense fallback={<p>Loading, please wait...</p>}>
                {node}
            </Suspense>
        )
    }

    const fetcher = useMemo(() => new Fetcher(getStoredAuthentication()), [])
    const [user, setUser] = useState<User | null>(null)

    const handleAuthSuccess = useCallback(
        async (auth: Authentication) => {
            fetcher.updateAuthentication(auth)
            const user = await fetchUser(fetcher)
            setUser(user)
            storeAuthentication(auth)
        },
        [fetcher],
    )

    useEffect(() => {
        const interval = setInterval(() => {
            fetcher.fetchAPIGet("auth/keep-alive")
        }, TOKEN_REFRESH_INTERVAL_MS)

        return () => clearInterval(interval)
    }, [fetcher])

    return (
        <div id="app">
            <FetcherContext.Provider value={fetcher}>
                <SignedInUserContext.Provider
                    value={{
                        user,
                        setUser,
                    }}>
                    <BrowserRouter basename={BASE}>
                        <Outlet />

                        <Routes>
                            <Route
                                path={"/login"}
                                element={suspense(
                                    <LoginPage onSuccess={handleAuthSuccess} />,
                                )}
                            />
                            <Route
                                path={"/register"}
                                element={suspense(
                                    <RegisterPage
                                        onSuccess={handleAuthSuccess}
                                    />,
                                )}
                            />

                            <Route path={"/"} element={suspense(<AppLayout />)}>
                                <Route
                                    path={"/"}
                                    element={suspense(
                                        <LoggedInPage>
                                            <HomePage />
                                        </LoggedInPage>,
                                    )}
                                />
                                <Route
                                    path={"/home"}
                                    element={suspense(
                                        <LoggedInPage>
                                            <HomePage />
                                        </LoggedInPage>,
                                    )}
                                />
                                <Route
                                    path={"/settings"}
                                    element={suspense(
                                        <LoggedInPage>
                                            <Settings />
                                        </LoggedInPage>,
                                    )}
                                />
                                <Route
                                    path={"/team/new"}
                                    element={suspense(<CreateTeamPage />)}
                                />
                                <Route
                                    path={"/team/:teamId"}
                                    element={suspense(
                                        <LoggedInPage>
                                            <TeamPanelPage />
                                        </LoggedInPage>,
                                    )}
                                />
                                <Route
                                    path={"/tactic/new"}
                                    element={suspense(<NewTacticPage />)}
                                />
                                <Route
                                    path={"/tactic/:tacticId/edit"}
                                    element={suspense(
                                        <LoggedInPage>
                                            <Editor guestMode={false} />
                                        </LoggedInPage>,
                                    )}
                                />
                                <Route
                                    path={"/tactic/:tacticId/view"}
                                    element={suspense(
                                        <LoggedInPage>
                                            <VisualizerPage guestMode={false} />
                                            ,
                                        </LoggedInPage>,
                                    )}
                                />
                                <Route
                                    path={"/tactic/view-guest"}
                                    element={suspense(
                                        <VisualizerPage guestMode={true} />,
                                    )}
                                />
                                <Route
                                    path={"/tactic/edit-guest"}
                                    element={suspense(
                                        <Editor guestMode={true} />,
                                    )}
                                />

                                <Route
                                    path={"*"}
                                    element={suspense(<NotFoundPage />)}
                                />
                            </Route>
                        </Routes>
                    </BrowserRouter>
                </SignedInUserContext.Provider>
            </FetcherContext.Provider>
        </div>
    )
}

async function fetchUser(fetcher: Fetcher): Promise<User> {
    const response = await fetcher.fetchAPIGet("user")

    if (!response.ok) {
        throw Error(
            "Could not retrieve user information : " + (await response.text()),
        )
    }

    return await response.json()
}

const STORAGE_AUTH_KEY = "token"

function getStoredAuthentication(): Authentication {
    const storedUser = localStorage.getItem(STORAGE_AUTH_KEY)
    return storedUser == null ? null : JSON.parse(storedUser)
}

function storeAuthentication(auth: Authentication) {
    localStorage.setItem(STORAGE_AUTH_KEY, JSON.stringify(auth))
}

interface LoggedInPageProps {
    children: ReactNode
}

enum UserFetchingState {
    FETCHING,
    FETCHED,
    ERROR,
}

function LoggedInPage({ children }: LoggedInPageProps) {
    const [user, setUser] = useUser()
    const fetcher = useAppFetcher()
    const [userFetchingState, setUserFetchingState] = useState(
        user === null ? UserFetchingState.FETCHING : UserFetchingState.FETCHED,
    )
    const location = useLocation()

    useEffect(() => {
        async function initUser() {
            try {
                const user = await fetchUser(fetcher)
                setUser(user)
                setUserFetchingState(UserFetchingState.FETCHED)
            } catch (e) {
                setUserFetchingState(UserFetchingState.ERROR)
            }
        }

        if (userFetchingState === UserFetchingState.FETCHING) initUser()
    }, [fetcher, setUser, userFetchingState])

    switch (userFetchingState) {
        case UserFetchingState.ERROR:
            return (
                <Navigate
                    to={"/login"}
                    replace
                    state={{ from: location.pathname }}
                />
            )
        case UserFetchingState.FETCHED:
            return children
        case UserFetchingState.FETCHING:
            return <p>Fetching user...</p>
    }
}

function AppLayout() {
    return (
        <>
            <Header />
            <Outlet />
        </>
    )
}

interface UserContext {
    user: User | null
    setUser: (user: User | null) => void
}

const SignedInUserContext = createContext<UserContext | null>(null)
const FetcherContext = createContext(new Fetcher())

export function useAppFetcher() {
    return useContext(FetcherContext)
}

export function useUser(): [User | null, (user: User | null) => void] {
    const { user, setUser } = useContext(SignedInUserContext)!
    return [user, setUser]
}
