import * as React from "react";

import { BrowserRouter, Route, Redirect } from "react-router-dom";

import AuthRoute from "../security/AuthRoute";
import { setSessionToken, removeSessionToken, getSessionToken } from "../security/Token";

import Dashboard from "../containers/Dashboard";
import LoginPage from "../containers/LoginPage";
import LevelListPage from "../containers/LevelList";
import LevelPage from "../containers/LevelPage";
import ReviewPage from "../containers/Review";
import SummaryPage from "../containers/SummaryPage";
import WelcomePage from "../pages/intro";

import Drawer from "../containers/Drawer";

import { BACKEND_URL } from "../config";

import { ILevel } from "../models/level";
import { TopTen } from "../models/learner";
import { IVocab } from "../models/vocab";
import { IReviewMetadata, ReviewType } from "../models/review";
import { IUser } from "../models/user";
import { IResponse } from "../models/server";

interface IProps {
    authenticated: boolean;

    user: IUser;
    setAuthenticated: (status: boolean) => void;
    setDidLogin: (status: boolean) => void;
    setUser: (user: IUser) => void;
    setLastReview: (meta: IReviewMetadata) => void;
    setUserScoreDelta: (delta: number) => void;
};

// TODO: Replace the sessionStorage with localStorage?
export default class Application extends React.Component<IProps> {
    componentDidMount() {
        // TODO: When asking the server if our session is still valid, a spinner
        //       should be shown
        const token = getSessionToken(window);
        if (token !== null && !this.props.authenticated) {
            this.checkAuthStatus(token).then(user => {
                this.props.setUser(user);
                this.props.setAuthenticated(true);
            }).catch(err => {
                // The token is invalid, so we should remove it
                removeSessionToken(window);
                this.props.setAuthenticated(false);
            });
        }
    }

    checkAuthStatus = (token: string): Promise<IUser> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/user/me`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": token,
                }),
            }).then(resp => resp.json(), err => rej(err))
                .then(data => {
                    if (data.error === "0") {
                        res(data.data);
                    } else {
                        rej(data);
                    }
                });
        });
    }

    getLevels = (): Promise<ILevel[]> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/levels`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": this.props.user.sessionToken,
                }),
            }).then(resp => resp.json(), err => rej(err))
                .then(data => {
                    if (data.error === "0") {
                        res(data.data.levels);
                    } else {
                        rej(data);
                    }
                });
        });
    }

    getLastReview = (): Promise<IReviewMetadata> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/user/lastReview`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": this.props.user.sessionToken,
                }),
            }).then(resp => resp.json(), err => rej(err))
                .then(data => {
                    if (data.error === "0") {
                        res(data.data);
                    } else {
                        rej(data);
                    }
                });
        });
    }

    // TODO: Type?
    setLastReview = (meta: IReviewMetadata, sm2: any, delta: number) => {
        // Update the state
        this.props.setLastReview(meta);
        this.props.setUserScoreDelta(delta);

        // Tell the server about the last review
        fetch(`${BACKEND_URL}/api/user/lastReview`, {
            headers: new Headers({
                "Content-Type": "application/json",
                "Token": this.props.user.sessionToken,
            }),
            method: "POST",
            body: JSON.stringify({
                meta,
                sm2,
                delta,
            }),
        }).then(resp => resp.json(), err => {
            console.log("Application::setLastReview: POSTing last results failed");
        });
    }

    getReviewQueue = (): Promise<IVocab[]> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/user/queue`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": this.props.user.sessionToken,
                }),
            }).then(resp => resp.json(), err => rej(err))
                .then(data => {
                    if (data.error === "0") {
                        res(data.data.queue);
                    } else {
                        rej(data);
                    }
                });
        });
    }

    getTopTenLearners = (): Promise<TopTen[]> => {
        // TODO: Deprecate?
        const id = this.props.user.classId;
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/class/${id}/topTen`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": this.props.user.sessionToken,
                }),
            }).then(resp => resp.json(),
                err => rej(err))
                .then(data => {
                    if (data.error === "0") {
                        res(data.data.topTen);
                    } else {
                        rej(data);
                    }
                });
        });
    }

    getNextLevel = (): Promise<ILevel> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/user/nextLevel`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": this.props.user.sessionToken,
                }),
            }).then(resp => resp.json(),
                err => rej(err))
                .then(data => {
                    if (data.error === "0") {
                        res(data.data);
                    } else {
                        rej(data);
                    }
                });
        });
    }

    getLevelVocab = (id: number): Promise<IVocab[]> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/level/${id}/vocab`, {
                method: "GET",
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": this.props.user.sessionToken,
                }),
            }).then(data => data.json(), err => {
                rej(err);
            }).then((resp: IResponse) => {
                if (resp.error === "0") {
                    res(resp.data.vocab);
                } else {
                    rej(resp);
                }
            });
        });
    }

    introDontShowAgain = (): void => {
        // NOTE: This is not a promise, as we do not care about any response
        //       being sent, since we don't need to update any client-side
        //       state.
        fetch(`${BACKEND_URL}/api/user/showWelcome`, {
            headers: new Headers({
                "Content-Type": "application/json",
                "Token": this.props.user.sessionToken,
            }),
            method: "POST",
            body: JSON.stringify({
                state: false,
            }),
        });
    }

    // TODO: Type?
    getDashboard = (): Promise<any> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/user/dashboard`, {
                headers: new Headers({
                    "Content-Type": "application/json",
                    "Token": this.props.user.sessionToken,
                }),
            })
                .then(resp => resp.json(), err => rej(err))
                .then(data => {
                    if (data.error === "200") {
                        res(data.data);
                    } else {
                        console.log("Application::getDashboard: Failed to get dashboard");
                        rej(data);
                    }
                });
        });
    }

    updateDoneLevels = (id: string): void => {
        fetch(`${BACKEND_URL}/api/user/level/${id}`, {
            headers: new Headers({
                "Content-Type": "application/json",
                "Token": this.props.user.sessionToken,
            }),
            method: "POST",
        });
    }

    login = (username: string, password: string): Promise<IUser | IResponse> => {
        return new Promise((res, rej) => {
            fetch(`${BACKEND_URL}/api/login`, {
                method: "POST",
                headers: new Headers({
                    "Content-Type": "application/json",
                }),
                body: JSON.stringify({
                    // NOTE: We will force HTTPS, so this should not be a
                    // problem
                    username,
                    password,
                }),
            }).then(data => data.json(), err => {
                // The fetch failed
                rej(err);
            }).then((resp: IResponse) => {
                if (resp.error === "0") {
                    // Successful login
                    this.props.setUser(resp.data);
                    this.props.setDidLogin(true);
                    setSessionToken(window, resp.data.sessionToken);
                    this.props.setAuthenticated(true);

                    res(resp.data);
                } else {
                    rej(resp);
                }
            });
        });
    }

    logout = () => {
        // NOTE: No promise, since we don't care about the result
        fetch(`${BACKEND_URL}/api/user/logout`, {
            headers: new Headers({
                "Content-Type": "application/json",
                "Token": this.props.user.sessionToken,
            }),
            method: "GET",
        });

        // Remove the session locally
        removeSessionToken(window);
        this.props.setAuthenticated(false);
    }

    // Checks whether the user is logged in
    isAuthenticated = () => {
        return this.props.authenticated;
    }

    render() {
        // TODO: Show a spinner before mounting the routes, so that we can
        //       check if were authenticated before doing any requests
        return <BrowserRouter
            basename="/app/">
            <div className="flex" >
                <Drawer logout={this.logout} />
                <div className="content">
                    <Route exact path="/" component={() => <Redirect to="/login" />} />
                    <Route exact path="/login" component={() => {
                        return <LoginPage login={this.login} />
                    }} />
                    <AuthRoute
                        isAuth={this.isAuthenticated}
                        path="/dashboard"
                        component={() => {
                            return <Dashboard
                                getDashboard={this.getDashboard} />
                        }} />
                    <AuthRoute
                        isAuth={this.isAuthenticated}
                        path="/welcome"
                        component={() => {
                            return <WelcomePage
                                dontShowAgain={this.introDontShowAgain} />
                        }} />
                    <AuthRoute
                        isAuth={this.isAuthenticated}
                        path="/levelList"
                        component={() => <LevelListPage
                            getLevels={this.getLevels} />} />
                    {/*We cannot use AuthRoute here, because match is undefined otherwise*/}
                    <Route
                        path="/level/:id"
                        component={({ match }) => {
                            if (this.isAuthenticated()) {
                                return <LevelPage
                                    id={match.params.id}
                                    levelVocab={this.getLevelVocab}
                                    setLastReview={this.setLastReview} />;
                            } else {
                                return <Redirect to="/login" />;
                            }
                        }} />
                    <Route
                        path="/review/level/:id"
                        component={({ match }) => {
                            if (this.isAuthenticated()) {
                                return <ReviewPage
                                    reviewType={ReviewType.LEVEL}
                                    updateDoneLevels={this.updateDoneLevels}
                                    levelId={match.params.id}
                                    vocabByLevel={this.getLevelVocab}
                                    setLastReview={this.setLastReview} />;
                            } else {
                                return <Redirect to="/login" />;
                            }
                        }} />
                    <AuthRoute
                        isAuth={this.isAuthenticated}
                        path="/review/queue"
                        component={() => {
                            return <ReviewPage
                                reviewType={ReviewType.QUEUE}
                                vocabByQueue={this.getReviewQueue}
                                drawerButtonState={this.drawerButtonState}
                                setLastReview={this.setLastReview} />;
                        }} />
                    <AuthRoute
                        isAuth={this.isAuthenticated}
                        path="/review/summary"
                        component={() => {
                            return <SummaryPage />
                        }} />
                </div>
            </div >
        </BrowserRouter >;
    }
};
