/*
 * form-creator-api. RESTful API to manage and answer forms.
 * Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
 * Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR
 *
 * This file is part of form-creator-api.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import { User } from "../../core/user";
import { Response, NextFunction } from "express";
import { Request } from "../apiTypes";
import { OptHandler } from "../../utils/optHandler";
import { eachSeries, map, waterfall } from "async";
import * as bcrypt from "bcrypt";
import * as jwt from "jsonwebtoken";
import { Form } from "../../core/form";
import { FormAnswer, FormAnswerOptions } from "../../core/formAnswer";

export class UserCtrl {

    public static signUp(req: Request, res: Response, next: NextFunction) {

        let newUser: User;

        waterfall([
            (callback: (err: Error, user?: User) => void) => {
                bcrypt.hash(req.body.hash, 10, (err: Error, hashedPw: string) => {
                    if (err) {
                        callback(err);
                        return;
                    }
                    try {
                        newUser = new User(OptHandler.User(req.body, hashedPw));
                    } catch (err) {
                        callback(err);
                        return;
                    }
                    callback(null, newUser);
                });
            },
            (user: User, callback: (err: Error) => void) => {
                req.db.user.write(user, (err: Error) => {
                    if (err) {
                        callback(err);
                        return;
                    } else {
                        res.json({
                            message: "User registered with sucess."
                        });
                        callback(null);
                        return;
                    }
                });
            }

        ], (error: Error) => {
            if (error) {
                res.status(500).json({
                    message: "Some error has ocurred. Check error property for details.",
                    error: error.message
                });
                return;
            }
        });
    }

    public static signIn(req: Request, res: Response, next: NextFunction) {

        waterfall([
            (callback: (err: Error, userId?: number) => void) => {
                req.db.user.executeVerifyEmail(req.body.email, (err: Error, id?: number) => {
                    if (id !== undefined) {
                        callback(null, id);
                        return;
                    }

                    if (err) {
                        callback(err);
                        return;
                    }

                    callback(new Error("Auth Failed"));
                });
            },
            (id: number, callback: (err: Error, user?: User) => void) => {
                req.db.user.read(id, (err: Error, user?: User) => {
                    if (err) {
                        callback(err);
                        return;
                    }
                    callback(null, user);
                });
            },
            (user: User, callback: (err: Error, user?: User) => void) => {
                bcrypt.compare(req.body.hash, user.hash, (err: Error, result?: boolean) => {
                    if (err !== undefined) {
                        callback(err);
                        return;
                    }
                    if (!result) {
                        callback(new Error("Auth failed"));
                        return;
                    }
                    callback(null, user);
                });
            },
            (user: User, callback: (err: Error) => void) => {
                jwt.sign(
                    {
                        email: user.email
                        , id: user.id
                    },
                    process.env.JWT_KEY
                    , {
                        expiresIn: "1h"
                    },
                    (err: Error, encoded: string) => {
                        if (err) {
                            callback(err);
                            return;
                        }
                        res.json({
                            message: "Authentication successful.",
                            token: encoded,
                            id: user.id
                        });
                        callback(null);
                    })
            }
        ], (err: Error) => {
            if (err) {
                res.status(500).json({
                    message: "Authentication fail."
                });
                return;
            }
            return;
        });
    }

    public static deleteData(req: Request, res: Response, next: NextFunction) {

        if (Object(req.userData).id !== Number(req.params.id)) {
            res.status(500).json({
                message: "Unauthorized action."
            })
            return;
        }

        req.db.user.delete(Object(req.userData).id, (err: Error) => {

            if (err) {
                res.status(500).json({
                    message: "Failed to delete user. Check error properties for details."
                    , error: err.message
                });
                return;
            }

            res.status(200).json({
                message: "User data deleted."
            });
        });
    }

    public static changePassword(req: Request, res: Response, next: NextFunction) {

        let newUser: User;

        waterfall([
            (callback: (err: Error, password?: string) => void) => {
                bcrypt.hash(req.body.hash, 10, (err: Error, hashedPw: string) => {
                    if (err) {
                        callback(err);
                        return;
                    }

                    callback(null, hashedPw);
                });
            },
            (password: string, callback: (err: Error, user?: User) => void) => {
                req.db.user.read(Object(req.userData).id, (err: Error, user?: User) => {

                    if (err) {
                        callback(err);
                        return;
                    }

                    newUser = new User(OptHandler.User(user, password));

                    callback(null, newUser);
                });
            },
            (user: User, callback: (err: Error) => void) => {
                req.db.user.update(user, Object(req.userData).id, (err: Error) => {
                    if (err) {
                        callback(err);
                        return;
                    } else {
                        res.json({
                            message: "Password changed with sucess."
                        });
                        callback(null);
                        return;
                    }
                });
            }

        ], (error: Error) => {
            if (error) {
                res.status(500).json({
                    message: "Some error has ocurred. Check error property for details.",
                    error: error.message
                });
                return;
            }
        });
    }

    public static listForms(req: Request, res: Response, next: NextFunction) {

        waterfall([
            (callback: (err: Error, forms?: any[]) => void) => {
                req.db.form.list(req.params.id, (err: Error, forms?: Form[]) => {
                    if (err) {
                        res.status(500).json({
                            message: "Could not list forms. Some error has occurred. Check error property for details.",
                            error: err
                        });
                        return;
                    }

                    const mappedForms = forms.map(form => ({
                        id: form.id
                        , title: form.title
                        , description: form.description
                        , answersNumber: 0
                        , date: ""
                    }));

                    callback(null, mappedForms);
                });
            },
            (forms: any[], callback: (err: Error, result?: Object[]) => void) => {
                eachSeries(forms, (form: any, innerCallback) => {
                    req.db.answer.readAll(form.id, (err: Error, resultAnswer?: FormAnswer[]) => {
                        if (err) {
                            innerCallback(err);
                            return;
                        }

                        form.answersNumber = resultAnswer.length;

                        innerCallback(null);
                    });
                }, (e) => {
                    callback(e, forms);
                });
            },
            (forms: any[], callback: (err: Error, result?: Object[]) => void) => {
                eachSeries(forms, (form: any, innerCallback) => {
                    req.db.form.readDate(form.id, (err: Error, dates?: any[]) => {
                        if (err) {
                            innerCallback(err);
                            return;
                        }

                        if (dates.length) {
                            form.date = dates.sort().slice(-1)[0].update_date;
                        }

                        innerCallback(null);
                    });
                }, (e) => {
                    callback(e, forms);
                });
            },
        ], (error: Error, forms) => {
            if (error) {
                res.status(500).json({
                    message: "Some error has ocurred. Check error property for details.",
                    error: error.message
                });
                return;
            }
            res.json(forms);
        });

    }

    public static update(req: Request, res: Response, next: NextFunction) {

        let newUser: User;
        try {
            newUser = new User(OptHandler.User(req.body));
        } catch (e) {
            res.status(500).json({
                message: "Invalid User. Check error property for details."
                , error: e.message
            });
            return;
        }
        waterfall([
            (callback: (err: Error, userResult?: User) => void) => {
                req.db.user.update(newUser, Object(req.userData).id, callback);
            }
        ], (err) => {
            if (err) {
                res.status(500).json({
                    message: "Could not update Form. Some error has ocurred. Check error property for details."
                    , error: err.message
                });
                return;
            }
            res.json({ message: "Updated" });
            return;
        });
    }
}