package passwordresetroute

import (
	"crypto/sha256"
	"fmt"
	"math/rand"
	"strings"
	"sync"
	"time"

	"bitbucket.org/nemt/nemt-portal-api/application/applicationservice"
	"bitbucket.org/nemt/nemt-portal-api/application/viewmodel"
	"bitbucket.org/nemt/nemt-portal-api/infra/config"
	"bitbucket.org/nemt/nemt-portal-api/server/router/routeutils"
	"github.com/labstack/echo"
)

const (
	tokenExpirationTime               = 90 // in minutes
	randomStringLength                = 15
	baseURL                           = "http://localhost:5000"
	passwordResetEmailSubject         = "Reset Your Password"
	passwordResetEmailMainBody        = "To reset your password click here or copy the following link and paste it into your browser: \n\n " + baseURL + "/#/reset-password/"
	passwordResetEmailFooter          = "\nThis link expires in 90 minutes"
	passwordResetCompleteEmailSubject = "Your Password Has been Reset"
	passwordResetCompleteEmailBody    = "Your password has been reset. To login click here or copy the following link and paste it into your browser: \n\n" + baseURL + "/#/login"
)

var (
	instance *controller
	once     sync.Once
)

type controller struct {
	svc *applicationservice.Service
	cfg *config.Config
}

func controllerInstance(svc *applicationservice.Service, cfg *config.Config) *controller {
	once.Do(func() {
		instance = &controller{
			svc: svc,
			cfg: cfg,
		}

		rand.Seed(time.Now().UTC().UnixNano())
	})
	return instance
}

func (c *controller) handleResetRequest(ctx echo.Context) error {
	userEmail, err := routeutils.GetAndValidateStringParam(ctx, "email", "mandatory field")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	//find if user with email exists
	user, err := c.svc.Users.GetByEmail(userEmail)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if user.Email == nil || (*user.Email != userEmail) {
		return routeutils.ResponseAPIOK(ctx, nil) //more secure, don't inform user (attacker) that email doesn't exists
	}

	//create and store reset token

	timeNow := time.Now()
	expirationTime := timeNow.Add(time.Minute * tokenExpirationTime)

	randomArray := make([]byte, randomStringLength)
	rand.Read(randomArray)
	token := fmt.Sprintf("%x", sha256.Sum256(randomArray))

	passwordResetEntry := viewmodel.PasswordReset{
		User:    user,
		Token:   token,
		Expires: expirationTime,
		Opened:  false,
		Used:    false,
	}

	_, err = c.svc.PasswordReset.CreatePasswordResetEntry(passwordResetEntry)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	//Send email with reset link
	notification := viewmodel.Notification{
		Type:    applicationservice.NotificationTypeEmail,
		From:    c.cfg.Email.Sender,
		To:      *user.Email,
		Subject: passwordResetEmailSubject,
		Message: passwordResetEmailMainBody + token + passwordResetEmailFooter,
	}

	notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, nil)
}

func (c *controller) handleResetComplete(ctx echo.Context) error {
	userToken, err := routeutils.GetAndValidateStringParam(ctx, "token", "mandatory field")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	var user viewmodel.User
	if err = ctx.Bind(&user); err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if len(strings.TrimSpace(user.Pass)) < 1 {
		routeutils.ResponseAPIPasswordResetFailed(ctx, "No password")
	}

	passwordResetEntry, err := c.svc.PasswordReset.GetByToken(userToken)
	if err != nil || len(passwordResetEntry.Token) < 1 || passwordResetEntry.Expires.Before(time.Now()) || passwordResetEntry.Used == true {
		routeutils.ResponseAPIPasswordResetFailed(ctx, "Token error")
	}

	fullUserData, err := c.svc.Users.GetByUUID(passwordResetEntry.User.ID, "")
	if err != nil {
		routeutils.ResponseAPIPasswordResetFailed(ctx, "User problem")
	}

	fmt.Println(fullUserData)

	//write new password in database
	//TODO

	if err := c.svc.PasswordReset.SetTokenUsed(userToken); err != nil {
		routeutils.ResponseAPIPasswordResetFailed(ctx, "Reset failed")
	}

	//Send email with reset link
	notification := viewmodel.Notification{
		Type:    applicationservice.NotificationTypeEmail,
		From:    c.cfg.Email.Sender,
		To:      *user.Email,
		Subject: passwordResetCompleteEmailSubject,
		Message: passwordResetCompleteEmailBody,
	}

	notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification)
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, nil)
}

func (c *controller) handleTokenOpen(ctx echo.Context) error {
	token, err := routeutils.GetAndValidateStringParam(ctx, "token", "mandatory field")
	if err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	if err := c.svc.PasswordReset.SetTokenOpened(token); err != nil {
		return routeutils.HandleAPIError(ctx, err)
	}

	return routeutils.ResponseAPIOK(ctx, nil)
}
