package handlers

import (
	"context"
	"encoding/json"
	"git.markbailey.dev/cervers/ptpp/lib/database"
	"git.markbailey.dev/cervers/ptpp/lib/database/dto"
	"net/http"
	"time"

	"git.markbailey.dev/cervers/ptpp/lib/logger"
	"git.markbailey.dev/cervers/ptpp/lib/session"
	"git.markbailey.dev/cervers/ptpp/util"
	"git.markbailey.dev/cervers/ptpp/view/user"
)

type UserHandler struct {
	logger  logger.ILogger
	session session.IManager
	db      database.IDB
}

func NewUserHandler(l logger.ILogger, s session.IManager, db database.IDB) *UserHandler {
	return &UserHandler{
		logger:  l,
		session: s,
		db:      db,
	}
}

func (u *UserHandler) Populate(w http.ResponseWriter, r *http.Request) error {
	existingOrg, err := u.db.Repo().FindOrganizationByName("CerbervsSoft")
	if existingOrg != nil && err == nil {
		return util.Redirect(w, r, "/signup", 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 = u.db.Repo().CreateOrganization(organization)
	if err != nil {
		return u.logDBError(err)
	}

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

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

func (u *UserHandler) SignIn(w http.ResponseWriter, r *http.Request) error {
	sess := u.session.SessionStart(w, r)

	if r.Method == http.MethodGet {
		username, ok := r.Context().Value("username").(string)
		if ok {
			if username == "" {
				return u.failWithFormError(w, r, "Invalid Username or Password.")
			} else {
				foundUser, err := u.db.Repo().FindUserByUsername(username)
				if foundUser == nil || err != nil {
					return u.failWithFormError(w, r, "Invalid Username or Password.")
				}

				if foundUser.Admin == 1 {
					return util.Redirect(w, r, "/admin/", http.StatusSeeOther, false)
				} else {
					return util.Redirect(w, r, "/", http.StatusSeeOther, false)
				}
			}
		}

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

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

		return nil
	}

	fd := &UserSignInForm{}

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

	foundUser, err := u.db.Repo().FindUserByUsername(fd.Username)
	if foundUser == nil || err != nil {
		return util.Redirect(w, r, "/signin", http.StatusSeeOther, false)
	}

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

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

	token, err := util.CreateTokenForUser(foundUser.Username)
	if err != nil {
		u.logger.Error(u.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 {
		u.logger.Error(err)
		return err
	}

	_, err = u.db.Repo().CreateHeartbeat(&dto.Heartbeat{
		User:      foundUser.Identifier,
		CreatedAt: time.Now(),
		IpAddr:    r.RemoteAddr,
		AuthToken: authToken,
	})
	if err != nil {
		return u.logDBError(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
	}

	u.logger.Info(foundUser.Username + " logged in")
	if foundUser.Admin == 1 {
		return util.Redirect(w, r, "/admin/", http.StatusSeeOther, false)
	} else {
		return util.Redirect(w, r, "/", http.StatusSeeOther, false)
	}
}

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 (u *UserHandler) SignUp(w http.ResponseWriter, r *http.Request) error {
	handlerSess := u.session.SessionStart(w, r)
	uname, ok := handlerSess.Get("username").(string)
	if !ok {
		uname = ""
	}
	if uname != "" {
		return util.Redirect(w, r, "/", http.StatusSeeOther, false)
	}

	if r.Method == http.MethodGet {
		if err := user.SignUpForm().Render(context.Background(), w); err != nil {
			u.logger.Error(u.logger.Wrap(err, "Error rendering sign up form"))
			return err
		}
		return nil
	}

	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 {
				u.logger.Error(u.logger.Wrap(err, "Error writing response"))
				return err
			}
			return nil
		}

		foundUser, err := u.db.Repo().FindUserByUsername(fd.Username)

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

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

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

		org, err := u.db.Repo().FindOrganizationByName("CerbervsSoft")
		if org == nil || err != nil {
			return u.logDBError(err)
		}

		newUser, err := u.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 {
			return u.logDBError(err)
		}

		_, err = u.db.Repo().CreateHeartbeat(&dto.Heartbeat{
			User:      newUser.Identifier,
			CreatedAt: time.Now(),
			IpAddr:    r.RemoteAddr,
			AuthToken: token,
		})
		if err != nil {
			return u.logDBError(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, "/signin", http.StatusSeeOther, false)
	}

	return nil
}

func (u *UserHandler) SignOut(w http.ResponseWriter, r *http.Request) error {
	sess := u.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 (u *UserHandler) logDBError(err error) error {
	u.db.Error(err)
	return err
}

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