package shared

import (
	"context"
	"encoding/json"
	"net/http"
	"time"

	"git.markbailey.dev/cerbervs/ptpp/app/controller"
	"git.markbailey.dev/cerbervs/ptpp/app/session"
	"git.markbailey.dev/cerbervs/ptpp/lib/database"
	"git.markbailey.dev/cerbervs/ptpp/lib/database/dto"
	"git.markbailey.dev/cerbervs/ptpp/lib/logger"
	"git.markbailey.dev/cerbervs/ptpp/util"

	"git.markbailey.dev/cerbervs/ptpp/view/user"
)

type PopulateHandler struct {
	controller.Controller
}

func (c PopulateHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
	c.Logger = l
	c.Db = d
	c.Session = s
	c.Ctx = ctx

	return c
}

func (c PopulateHandler) Get(w http.ResponseWriter, r *http.Request) error {
	existingOrg, err := c.Db.Repo().FindOrganizationByName("CerbervsSoft")
	if existingOrg != nil && err == nil {
		return util.Redirect(w, r, c.Ctx.GetRouteByName("app.user.sign_up"), http.StatusSeeOther, false)
	}

	authToken, err := util.CreateTokenForUser("CerbervsSoft")
	if err != nil {
		return err
	}

	organization := dto.Organization{
		Name:       "CerbervsSoft",
		OwnerName:  "Mark Bailey",
		OwnerEmail: "email@provider.com",
		OwnerPhone: "+11111111111",
		Authorized: 1,
		AuthToken:  authToken,
		CreatedAt:  time.Now(),
	}
	_, err = c.Db.Repo().CreateOrganization(organization)
	if err != nil {
		c.Db.Error(err)
		return err
	}

	return util.Redirect(w, r, c.Ctx.GetRouteByName("app.user.sign_up"), http.StatusSeeOther, false)
}

type SignInHandler struct {
	controller.Controller
}

func (c SignInHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
	c.Logger = l
	c.Db = d
	c.Session = s
    c.Ctx = ctx

	return c
}

type UserSignInForm struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

func (c SignInHandler) Get(w http.ResponseWriter, r *http.Request) error {
	sess := c.Session.SessionStart(w, r)

	username, ok := r.Context().Value("username").(string)
	if ok {
		if username == "" {
			return failWithFormError(w, r, "Invalid Username or Password.", sess)
		} else {
			foundUser, err := c.Db.Repo().FindUserByUsername(username)
			if foundUser == nil || err != nil {
				return failWithFormError(w, r, "Invalid Username or Password.", sess)
			}

			if foundUser.Admin == 1 {
				return util.Redirect(w, r, c.Ctx.GetRouteByName("app.admin.index"), http.StatusSeeOther, false)
			} else {
				return util.Redirect(w, r, c.Ctx.GetRouteByName("app.index"), http.StatusSeeOther, false)
			}
		}
	}

	formError, ok := sess.Get("formError").(string)
	if !ok {
		formError = ""
	}

	if err := user.SignIn(formError, c.Ctx).Render(context.Background(), w); err != nil {
		c.Logger.Error(c.Logger.Wrap(err, "Error rendering sign in form"))
		return err
	}

	return nil
}

func (c SignInHandler) Post(w http.ResponseWriter, r *http.Request) error {
	sess := c.Session.SessionStart(w, r)

	fd := &UserSignInForm{}

	jsonDecoder := json.NewDecoder(r.Body)
	if err := jsonDecoder.Decode(&fd); err != nil {
		c.Logger.Error(c.Logger.Wrap(err, "Error decoding JSON"))
		return err
	}

	foundUser, err := c.Db.Repo().FindUserByUsername(fd.Username)
	if foundUser == nil || err != nil {
		return util.Redirect(w, r, c.Ctx.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, false)
	}

	authenticated, err := util.CheckPassword(fd.Password, foundUser.Password)
	if err != nil || !authenticated {
		return failWithFormError(w, r, "Invalid Username or Password.", sess)
	}

	err = sess.Set("username", foundUser.Username)
	if err != nil {
		return err
	}

	token, err := util.CreateTokenForUser(foundUser.Username)
	if err != nil {
		c.Logger.Error(c.Logger.Wrap(err, "Error creating token"))
		return err
	}

	err = sess.Set("token", token)
	if err != nil {
		return err
	}

	authToken, err := util.CreateTokenForUser(foundUser.Username)
	if err != nil {
		c.Logger.Error(err)
		return err
	}

	_, err = c.Db.Repo().CreateHeartbeat(&dto.Heartbeat{
		User:      foundUser.Identifier,
		CreatedAt: time.Now(),
		IpAddr:    r.RemoteAddr,
		AuthToken: authToken,
	})
	if err != nil {
		c.Db.Error(err)
		return err
	}

	jwtToken, err := util.CreateTokenForUser(fd.Username)
	if err != nil {
		return err
	}

	http.SetCookie(w, &http.Cookie{
		Name:    "token",
		Value:   jwtToken,
		Expires: time.Now().Add(time.Hour * 1),
		Path:    "/",
	})

	err = sess.Delete("formError")
	if err != nil {
		return err
	}

	c.Logger.Info(foundUser.Username + " logged in")
	if foundUser.Admin == 1 {
		return util.Redirect(w, r, c.Ctx.GetRouteByName("app.admin.index"), http.StatusSeeOther, false)
	} else {
		return util.Redirect(w, r, c.Ctx.GetRouteByName("app.index"), http.StatusSeeOther, false)
	}
}

type SignUpHandler struct {
	controller.Controller
}

func (c SignUpHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
	c.Logger = l
	c.Db = d
	c.Session = s
	c.Ctx = ctx

	return c
}

type UserSignUpForm struct {
	Name                 string `json:"name"`
	Email                string `json:"email"`
	Username             string `json:"username"`
	Password             string `json:"password"`
	PasswordConfirmation string `json:"password_confirmation"`
}

func (c SignUpHandler) Get(w http.ResponseWriter, r *http.Request) error {
	sess := c.Session.SessionStart(w, r)

	uname, ok := sess.Get("username").(string)
	if !ok {
		uname = ""
	}
	if uname != "" {
		return util.Redirect(w, r, "/", http.StatusSeeOther, false)
	}

	if err := user.SignUpForm().Render(context.Background(), w); err != nil {
		c.Logger.Error(c.Logger.Wrap(err, "Error rendering sign up form"))
		return err
	}
	return nil
}

func (c SignUpHandler) Post(w http.ResponseWriter, r *http.Request) error {
	fd := &UserSignUpForm{}

	jsonDecoder := json.NewDecoder(r.Body)
	if err := jsonDecoder.Decode(&fd); err == nil {
		if fd.Password != fd.PasswordConfirmation {
			if _, err := w.Write([]byte("Passwords don't match")); err != nil {
				c.Logger.Error(c.Logger.Wrap(err, "Error writing response"))
				return err
			}
			return nil
		}

		foundUser, err := c.Db.Repo().FindUserByUsername(fd.Username)
		if err != nil {
			c.Db.Error(err)
			return err
		}

		if foundUser != nil {
			if _, err := w.Write([]byte("Invalid username. Please try another")); err != nil {
				c.Logger.Error(c.Logger.Wrap(err, "Error writing response"))
			}
			return nil
		}

		token, err := util.CreateTokenForUser(fd.Username)
		if err != nil {
			c.Logger.Error(c.Logger.Wrap(err, "Error creating token"))
			return err
		}

		password, err := util.HashPassword(fd.PasswordConfirmation)
		if err != nil {
			c.Logger.Error(c.Logger.Wrap(err, "Error hashing password"))
			return err
		}

		org, err := c.Db.Repo().FindOrganizationByName("CerbervsSoft")
		if org == nil || err != nil {
			c.Db.Error(err)
			return err
		}

		newUser, err := c.Db.Repo().CreateUser(dto.User{
			Username:     fd.Username,
			Password:     password,
			Name:         fd.Name,
			Email:        fd.Email,
			AuthToken:    token,
			Authorized:   1,
			Admin:        1,
			Organization: org.Identifier,
		})
		if err != nil {
			c.Db.Error(err)
			return err
		}

		_, err = c.Db.Repo().CreateHeartbeat(&dto.Heartbeat{
			User:      newUser.Identifier,
			CreatedAt: time.Now(),
			IpAddr:    r.RemoteAddr,
			AuthToken: token,
		})
		if err != nil {
			c.Db.Error(err)
			return err
		}

		jwtToken, err := util.CreateTokenForUser(fd.Username)
		if err != nil {
			return err
		}

		http.SetCookie(w, &http.Cookie{
			Name:    "token",
			Value:   jwtToken,
			Expires: time.Now().Add(time.Hour * 1),
			Path:    "/",
		})

		return util.Redirect(w, r, "/sign-in", http.StatusSeeOther, false)
	}

	return nil
}

type SignOutHandler struct {
	controller.Controller
}

func (c SignOutHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
	c.Logger = l
	c.Db = d
	c.Session = s
	c.Ctx = ctx

	return c
}

func (c SignOutHandler) Get(w http.ResponseWriter, r *http.Request) error {
	sess := c.Session.SessionStart(w, r)

	err := sess.Delete("username")
	if err != nil {
		return err
	}

	err = sess.Delete("token")
	if err != nil {
		return err
	}

	http.SetCookie(w, &http.Cookie{
		Name:    "token",
		Value:   "",
		Expires: time.Now().Add(-time.Hour),
		Path:    "/",
	})

	return util.Redirect(w, r, "/", http.StatusSeeOther, true)
}

func failWithFormError(w http.ResponseWriter, r *http.Request, formError string, sess session.ISession) error {
	err := sess.Set("formError", formError)
	if err != nil {
		return err
	}
	return util.Redirect(w, r, r.URL.Path, http.StatusSeeOther, false)
}
