import React, { Component } from "react";
import {
    withStyles,
    Paper,
    Typography,
    Button,
    TextField,
    Fade,
    Link,
    CircularProgress,
    Tooltip,
    Dialog,
    DialogTitle,
    DialogActions,
    DialogContent,
    List,
    ListItem,
    ListItemText,
    ListItemAvatar,
    ListItemSecondaryAction,
    IconButton
} from "@material-ui/core";
import { styles } from "./WelcomePage.styles";
import Mastodon from "megalodon";
import { SaveClientSession } from "../types/SessionData";
import {
    createHyperspaceApp,
    getRedirectAddress,
    inDisallowedDomains,
    instancesBearerKey
} from "../utilities/login";
import { parseUrl } from "query-string";
import { getConfig } from "../utilities/settings";
import { isDarwinApp } from "../utilities/desktop";
import axios from "axios";
import { withSnackbar, withSnackbarProps } from "notistack";
import { Config } from "../types/Config";
import {
    addAccountToRegistry,
    getAccountRegistry,
    loginWithAccount,
    removeAccountFromRegistry
} from "../utilities/accounts";
import { Account, MultiAccount } from "../types/Account";

import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import CloseIcon from "@material-ui/icons/Close";

interface IWelcomeProps extends withSnackbarProps {
    classes: any;
}

interface IWelcomeState {
    logoUrl?: string;
    backgroundUrl?: string;
    brandName?: string;
    registerBase?: string;
    federates?: boolean;
    proceedToGetCode: boolean;
    user: string;
    userInputError: boolean;
    userInputErrorMessage: string;
    clientId?: string;
    clientSecret?: string;
    authUrl?: string;
    foundSavedLogin: boolean;
    authorizing: boolean;
    license?: string;
    repo?: string;
    defaultRedirectAddress: string;
    openAuthDialog: boolean;
    authCode: string;
    emergencyMode: boolean;
    version: string;
    willAddAccount: boolean;
}

class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
    client: any;

    constructor(props: any) {
        super(props);

        this.state = {
            proceedToGetCode: false,
            user: "",
            userInputError: false,
            foundSavedLogin: false,
            authorizing: false,
            userInputErrorMessage: "",
            defaultRedirectAddress: "",
            openAuthDialog: false,
            authCode: "",
            emergencyMode: false,
            version: "",
            willAddAccount: false
        };

        getConfig()
            .then((result: any) => {
                if (result !== undefined) {
                    let config: Config = result;
                    if (result.location === "dynamic") {
                        console.warn(
                            "Redirect URI is set to dynamic, which may affect how sign-in works for some users. Careful!"
                        );
                    }
                    if (
                        inDisallowedDomains(result.registration.defaultInstance)
                    ) {
                        console.warn(
                            `The default instance field in config.json contains an unsupported domain (${result.registration.defaultInstance}), so it's been reset to mastodon.social.`
                        );
                        result.registration.defaultInstance = "mastodon.social";
                    }
                    this.setState({
                        logoUrl: config.branding
                            ? result.branding.logo
                            : "logo.png",
                        backgroundUrl: config.branding
                            ? result.branding.background
                            : "background.png",
                        brandName: config.branding
                            ? result.branding.name
                            : "Hyperspace",
                        registerBase: config.registration
                            ? result.registration.defaultInstance
                            : "",
                        federates: config.federation.universalLogin,
                        license: config.license.url,
                        repo: config.repository,
                        defaultRedirectAddress:
                            config.location != "dynamic"
                                ? config.location
                                : `https://${window.location.host}`,
                        version: config.version
                    });
                }
            })
            .catch(() => {
                console.error(
                    "config.json is missing. If you want to customize Hyperspace, please include config.json"
                );
            });
    }

    componentDidMount() {
        if (localStorage.getItem("login")) {
            this.getSavedSession();
            this.setState({
                foundSavedLogin: true
            });
            this.checkForToken();
        }
    }

    updateUserInfo(user: string) {
        this.setState({ user });
    }

    updateAuthCode(code: string) {
        this.setState({ authCode: code });
    }

    toggleAuthDialog() {
        this.setState({ openAuthDialog: !this.state.openAuthDialog });
    }

    readyForAuth() {
        if (localStorage.getItem("baseurl")) {
            return true;
        } else {
            return false;
        }
    }

    clear() {
        localStorage.removeItem("access_token");
        localStorage.removeItem("baseurl");
    }

    getSavedSession() {
        let loginData = localStorage.getItem("login");
        if (loginData) {
            let session: SaveClientSession = JSON.parse(loginData);
            this.setState({
                clientId: session.clientId,
                clientSecret: session.clientSecret,
                authUrl: session.authUrl,
                emergencyMode: session.emergency
            });
        }
    }

    startEmergencyLogin() {
        if (!this.state.emergencyMode) {
            this.createEmergencyLogin();
        }
        this.toggleAuthDialog();
    }

    startRegistration() {
        if (this.state.registerBase) {
            return "https://" + this.state.registerBase + "/auth/sign_up";
        } else {
            return "https://joinmastodon.org/#getting-started";
        }
    }

    watchUsernameField(event: any) {
        if (event.keyCode === 13) this.startLogin();
    }

    watchAuthField(event: any) {
        if (event.keyCode === 13) this.authorizeEmergencyLogin();
    }

    getLoginUser(user: string) {
        if (user.includes("@")) {
            if (this.state.federates) {
                let newUser = user;
                this.setState({ user: newUser });
                return "https://" + newUser.split("@")[1];
            } else {
                let newUser = `${user}@${
                    this.state.registerBase
                        ? this.state.registerBase
                        : "mastodon.social"
                }`;
                this.setState({ user: newUser });
                return (
                    "https://" +
                    (this.state.registerBase
                        ? this.state.registerBase
                        : "mastodon.social")
                );
            }
        } else {
            let newUser = `${user}@${
                this.state.registerBase
                    ? this.state.registerBase
                    : "mastodon.social"
            }`;
            this.setState({ user: newUser });
            return (
                "https://" +
                (this.state.registerBase
                    ? this.state.registerBase
                    : "mastodon.social")
            );
        }
    }

    startLogin() {
        let error = this.checkForErrors();
        if (!error) {
            const scopes = "read write follow";
            const baseurl = this.getLoginUser(this.state.user);
            localStorage.setItem("baseurl", baseurl);
            createHyperspaceApp(
                this.state.brandName ? this.state.brandName : "Hyperspace",
                scopes,
                baseurl,
                getRedirectAddress(this.state.defaultRedirectAddress)
            ).then((resp: any) => {
                let saveSessionForCrashing: SaveClientSession = {
                    clientId: resp.clientId,
                    clientSecret: resp.clientSecret,
                    authUrl: resp.url,
                    emergency: false
                };
                localStorage.setItem(
                    "login",
                    JSON.stringify(saveSessionForCrashing)
                );
                this.setState({
                    clientId: resp.clientId,
                    clientSecret: resp.clientSecret,
                    authUrl: resp.url,
                    proceedToGetCode: true
                });
            });
        } else {
        }
    }

    createEmergencyLogin() {
        console.log("Creating an emergency login...");
        const scopes = "read write follow";
        const baseurl =
            localStorage.getItem("baseurl") ||
            this.getLoginUser(this.state.user);
        Mastodon.registerApp(
            this.state.brandName ? this.state.brandName : "Hyperspace",
            {
                scopes: scopes
            },
            baseurl
        ).then((appData: any) => {
            let saveSessionForCrashing: SaveClientSession = {
                clientId: appData.clientId,
                clientSecret: appData.clientSecret,
                authUrl: appData.url,
                emergency: true
            };
            localStorage.setItem(
                "login",
                JSON.stringify(saveSessionForCrashing)
            );
            this.setState({
                clientId: appData.clientId,
                clientSecret: appData.clientSecret,
                authUrl: appData.url
            });
        });
    }

    authorizeEmergencyLogin() {
        let redirAddress =
            this.state.defaultRedirectAddress === "desktop"
                ? "hyperspace://hyperspace/app"
                : this.state.defaultRedirectAddress;
        window.location.href = `${redirAddress}/?code=${this.state.authCode}#/`;
    }

    resumeLogin() {
        let loginData = localStorage.getItem("login");
        if (loginData) {
            let session: SaveClientSession = JSON.parse(loginData);
            this.setState({
                clientId: session.clientId,
                clientSecret: session.clientSecret,
                authUrl: session.authUrl,
                emergencyMode: session.emergency,
                proceedToGetCode: true
            });
        }
    }

    checkForErrors(): boolean {
        let userInputError = false;
        let userInputErrorMessage = "";

        if (this.state.user === "") {
            userInputError = true;
            userInputErrorMessage = "Username cannot be blank.";
            this.setState({ userInputError, userInputErrorMessage });
            return true;
        } else {
            if (this.state.user.includes("@")) {
                if (this.state.federates && this.state.federates === true) {
                    let baseUrl = this.state.user.split("@")[1];
                    if (inDisallowedDomains(baseUrl)) {
                        this.setState({
                            userInputError: true,
                            userInputErrorMessage: `Signing in with an account from ${baseUrl} isn't supported.`
                        });
                        return true;
                    } else {
                        axios
                            .get(
                                "https://instances.social/api/1.0/instances/show?name=" +
                                    baseUrl,
                                {
                                    headers: {
                                        Authorization: `Bearer ${instancesBearerKey}`
                                    }
                                }
                            )
                            .catch((err: Error) => {
                                let userInputError = true;
                                let userInputErrorMessage =
                                    "Instance name is invalid.";
                                this.setState({
                                    userInputError,
                                    userInputErrorMessage
                                });
                                return true;
                            });
                    }
                } else if (
                    this.state.user.includes(
                        this.state.registerBase
                            ? this.state.registerBase
                            : "mastodon.social"
                    )
                ) {
                    this.setState({ userInputError, userInputErrorMessage });
                    return false;
                } else {
                    userInputError = true;
                    userInputErrorMessage =
                        "You cannot sign in with this username.";
                    this.setState({ userInputError, userInputErrorMessage });
                    return true;
                }
            } else {
                this.setState({ userInputError, userInputErrorMessage });
                return false;
            }
            this.setState({ userInputError, userInputErrorMessage });
            return false;
        }
    }

    checkForToken() {
        let location = window.location.href;
        if (location.includes("?code=")) {
            let code = parseUrl(location).query.code as string;
            this.setState({ authorizing: true });
            let loginData = localStorage.getItem("login");
            if (loginData) {
                let clientLoginSession: SaveClientSession = JSON.parse(
                    loginData
                );
                Mastodon.fetchAccessToken(
                    clientLoginSession.clientId,
                    clientLoginSession.clientSecret,
                    code,
                    localStorage.getItem("baseurl") as string,
                    this.state.emergencyMode
                        ? undefined
                        : clientLoginSession.authUrl.includes(
                              "urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
                          )
                        ? undefined
                        : window.location.protocol === "hyperspace:"
                        ? "hyperspace://hyperspace/app/"
                        : `https://${window.location.host}`
                )
                    .then((tokenData: any) => {
                        localStorage.setItem(
                            "access_token",
                            tokenData.access_token
                        );
                        window.location.href =
                            window.location.protocol === "hyperspace:"
                                ? "hyperspace://hyperspace/app/"
                                : `https://${window.location.host}/#/`;
                    })
                    .catch((err: Error) => {
                        this.props.enqueueSnackbar(
                            `Couldn't authorize ${
                                this.state.brandName
                                    ? this.state.brandName
                                    : "Hyperspace"
                            }: ${err.name}`,
                            { variant: "error" }
                        );
                        console.error(err.message);
                    });
            }
        }
    }

    titlebar() {
        const { classes } = this.props;
        if (isDarwinApp()) {
            return (
                <div className={classes.titleBarRoot}>
                    <Typography className={classes.titleBarText}>
                        {this.state.brandName
                            ? this.state.brandName
                            : "Hyperspace"}
                    </Typography>
                </div>
            );
        }
    }

    showMultiAccount() {
        const { classes } = this.props;
        return (
            <div>
                <Typography variant="h5">Select an account</Typography>
                <Typography>from the list below or add a new one</Typography>

                <List>
                    {getAccountRegistry().map(
                        (account: MultiAccount, index: number) => (
                            <ListItem
                                onClick={() => {
                                    loginWithAccount(account);
                                    window.location.href =
                                        window.location.protocol ===
                                        "hyperspace:"
                                            ? "hyperspace://hyperspace/app/"
                                            : `https://${window.location.host}/#/`;
                                }}
                                button={true}
                            >
                                <ListItemAvatar>
                                    <AccountCircleIcon color="action" />
                                </ListItemAvatar>
                                <ListItemText
                                    primary={`@${account.username}`}
                                    secondary={account.host}
                                />
                                <ListItemSecondaryAction>
                                    <IconButton
                                        onClick={(e: any) => {
                                            e.preventDefault();
                                            removeAccountFromRegistry(index);
                                            window.location.reload();
                                        }}
                                    >
                                        <CloseIcon />
                                    </IconButton>
                                </ListItemSecondaryAction>
                            </ListItem>
                        )
                    )}
                </List>
                <div className={classes.middlePadding} />

                <Button
                    onClick={() => {
                        this.setState({ willAddAccount: true });
                        this.clear();
                    }}
                    color={"primary"}
                    variant={"contained"}
                >
                    Add Account
                </Button>
            </div>
        );
    }

    showLanding() {
        const { classes } = this.props;
        return (
            <div>
                <Typography variant="h5">Sign in</Typography>
                <Typography>with your Mastodon account</Typography>
                <div className={classes.middlePadding} />
                <TextField
                    variant="outlined"
                    label="Username"
                    fullWidth
                    placeholder="example@mastodon.example"
                    onChange={event => this.updateUserInfo(event.target.value)}
                    onKeyDown={event => this.watchUsernameField(event)}
                    error={this.state.userInputError}
                    onBlur={() => this.checkForErrors()}
                />
                {this.state.userInputError ? (
                    <Typography color="error">
                        {this.state.userInputErrorMessage}
                    </Typography>
                ) : null}
                <br />
                {this.state.registerBase && this.state.federates ? (
                    <Typography variant="caption">
                        Not from{" "}
                        <b>
                            {this.state.registerBase
                                ? this.state.registerBase
                                : "noinstance"}
                        </b>
                        ? Sign in with your{" "}
                        <Link
                            href="https://docs.joinmastodon.org/usage/decentralization/#addressing-people"
                            target="_blank"
                            rel="noopener noreferrer"
                            color="secondary"
                        >
                            full username
                        </Link>
                        .
                    </Typography>
                ) : null}
                <br />
                {this.state.foundSavedLogin ? (
                    <Typography>
                        Signing in from a previous session?{" "}
                        <Link
                            className={classes.welcomeLink}
                            onClick={() => this.resumeLogin()}
                        >
                            Continue login
                        </Link>
                        .
                    </Typography>
                ) : null}

                <div className={classes.middlePadding} />
                <div style={{ display: "flex" }}>
                    <Tooltip title="Create account on site">
                        <Button
                            href={this.startRegistration()}
                            target="_blank"
                            rel="noreferrer"
                        >
                            Create account
                        </Button>
                    </Tooltip>
                    <div className={classes.flexGrow} />
                    <Tooltip title="Continue sign-in">
                        <Button
                            color="primary"
                            variant="contained"
                            onClick={() => this.startLogin()}
                        >
                            Next
                        </Button>
                    </Tooltip>
                </div>
            </div>
        );
    }

    showLoginAuth() {
        const { classes } = this.props;
        return (
            <div>
                <Typography variant="h5">
                    Howdy,{" "}
                    {this.state.user ? this.state.user.split("@")[0] : "user"}
                </Typography>
                <Typography>
                    To continue, finish signing in on your instance's website
                    and authorize{" "}
                    {this.state.brandName ? this.state.brandName : "Hyperspace"}
                    .
                </Typography>
                <div className={classes.middlePadding} />
                <div style={{ display: "flex" }}>
                    <div className={classes.flexGrow} />
                    <Button
                        color="primary"
                        variant="contained"
                        size="large"
                        href={this.state.authUrl ? this.state.authUrl : ""}
                    >
                        Authorize
                    </Button>
                    <div className={classes.flexGrow} />
                </div>
                <div className={classes.middlePadding} />
                <Typography>
                    Having trouble signing in?{" "}
                    <Link
                        onClick={() => this.startEmergencyLogin()}
                        className={classes.welcomeLink}
                    >
                        Sign in with a code.
                    </Link>
                </Typography>
            </div>
        );
    }

    showAuthDialog() {
        const { classes } = this.props;
        return (
            <Dialog
                open={this.state.openAuthDialog}
                disableBackdropClick
                disableEscapeKeyDown
                maxWidth="sm"
                fullWidth={true}
            >
                <DialogTitle>Authorize with a code</DialogTitle>
                <DialogContent>
                    <Typography paragraph>
                        If you're having trouble authorizing Hyperspace, you can
                        manually request for an authorization code. Click
                        'Request Code' and then paste the code in the
                        authorization code box to continue.
                    </Typography>
                    <Button
                        color="primary"
                        variant="contained"
                        href={this.state.authUrl ? this.state.authUrl : ""}
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        Request Code
                    </Button>
                    <br />
                    <br />
                    <TextField
                        variant="outlined"
                        label="Authorization code"
                        fullWidth
                        onChange={event =>
                            this.updateAuthCode(event.target.value)
                        }
                        onKeyDown={event => this.watchAuthField(event)}
                    />
                </DialogContent>
                <DialogActions>
                    <Button onClick={() => this.toggleAuthDialog()}>
                        Cancel
                    </Button>
                    <Button
                        color="secondary"
                        onClick={() => this.authorizeEmergencyLogin()}
                    >
                        Authorize
                    </Button>
                </DialogActions>
            </Dialog>
        );
    }

    showAuthorizationLoader() {
        const { classes } = this.props;
        return (
            <div>
                <Typography variant="h5">Authorizing</Typography>
                <Typography>
                    Please wait while Hyperspace authorizes with Mastodon. This
                    shouldn't take long...
                </Typography>
                <div className={classes.middlePadding} />
                <div style={{ display: "flex" }}>
                    <div className={classes.flexGrow} />
                    <CircularProgress />
                    <div className={classes.flexGrow} />
                </div>
                <div className={classes.middlePadding} />
            </div>
        );
    }

    render() {
        const { classes } = this.props;
        return (
            <div>
                {this.titlebar()}
                <div
                    className={classes.root}
                    style={{
                        backgroundImage: `url(${
                            this.state !== null
                                ? this.state.backgroundUrl
                                : "background.png"
                        })`
                    }}
                >
                    <Paper className={classes.paper}>
                        <img
                            className={classes.logo}
                            alt={
                                this.state ? this.state.brandName : "Hyperspace"
                            }
                            src={this.state ? this.state.logoUrl : "logo.png"}
                        />
                        <br />
                        <Fade in={true}>
                            {this.state.authorizing
                                ? this.showAuthorizationLoader()
                                : this.state.proceedToGetCode
                                ? this.showLoginAuth()
                                : getAccountRegistry().length > 0 &&
                                  !this.state.willAddAccount
                                ? this.showMultiAccount()
                                : this.showLanding()}
                        </Fade>
                        <br />
                        <Typography variant="caption">
                            &copy; {new Date().getFullYear()}{" "}
                            {this.state.brandName &&
                            this.state.brandName !== "Hyperspace"
                                ? `${this.state.brandName} developers and the `
                                : ""}{" "}
                            <Link
                                className={classes.welcomeLink}
                                href="https://hyperspace.marquiskurt.net"
                                target="_blank"
                                rel="noreferrer"
                            >
                                Hyperspace
                            </Link>{" "}
                            developers. All rights reserved.
                        </Typography>
                        <Typography variant="caption">
                            {this.state.repo ? (
                                <span>
                                    <Link
                                        className={classes.welcomeLink}
                                        href={
                                            this.state.repo
                                                ? this.state.repo
                                                : "https://github.com/hyperspacedev"
                                        }
                                        target="_blank"
                                        rel="noreferrer"
                                    >
                                        Source code
                                    </Link>{" "}
                                    |{" "}
                                </span>
                            ) : null}
                            <Link
                                className={classes.welcomeLink}
                                href={
                                    this.state.license
                                        ? this.state.license
                                        : "https://www.apache.org/licenses/LICENSE-2.0"
                                }
                                target="_blank"
                                rel="noreferrer"
                            >
                                License
                            </Link>{" "}
                            |
                            <Link
                                className={classes.welcomeLink}
                                href="https://github.com/hyperspacedev/hyperspace/issues/new"
                                target="_blank"
                                rel="noreferrer"
                            >
                                File an Issue
                            </Link>
                        </Typography>
                        <Typography variant="caption" color="textSecondary">
                            {this.state.brandName
                                ? this.state.brandName
                                : "Hypersapce"}{" "}
                            v.
                            {this.state.version}{" "}
                            {this.state.brandName &&
                            this.state.brandName !== "Hyperspace"
                                ? "(Hyperspace-like)"
                                : null}
                        </Typography>
                    </Paper>
                    {this.showAuthDialog()}
                </div>
            </div>
        );
    }
}

export default withStyles(styles)(withSnackbar(WelcomePage));
