package controllers

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"
	"zatobay_api/database"
	"zatobay_api/models"
	datatable "zatobay_api/utils/datatable"

	"github.com/dgrijalva/jwt-go"
	"github.com/gofiber/fiber/v2"
	"golang.org/x/crypto/bcrypt"
)

const SecretKey = "secret"

type MyError struct {
	msg string
}

type FilterAjaxUser struct {
	Name    string `json:"user_name" url:"user_name" query:"user_name"`
	Page    string `json:"page" url:"page" query:"page"`
	PerPage string `json:"length" url:"length" query:"length"`
	Sort    string `json:"column" url:"column" query:"column"`
	SortDir string `json:"dir" url:"dir" query:"dir"`
	Draw    string `json:"draw" url:"draw" query:"draw"`
}

func CreateUser(c *fiber.Ctx) error {
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return c.Status(400).JSON(err.Error())
	}

	password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14)

	user := models.User{
		Name:      data["name"],
		Email:     data["email"],
		Password:  password,
		RoleId:    data["role_id"],
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	create_user := database.DB.Create(&user)

	if create_user.Error != nil {
		return c.Status(400).JSON(create_user.Error)
	}

	return c.Status(200).JSON(fiber.Map{
		"message": "success",
		"data":    user,
	})
}

func Login(c *fiber.Ctx) error {
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return err
	}

	var user models.User

	database.DB.Where("email = ?", data["email"]).First(&user)

	if user.Id == 0 {
		c.Status(fiber.StatusNotFound)
		return c.JSON(fiber.Map{
			"message": "user not found",
		})
	}

	if err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])); err != nil {
		c.Status(fiber.StatusBadRequest)
		return c.JSON(fiber.Map{
			"message": "incorrect password",
		})
	}

	claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
		Issuer:    strconv.Itoa(int(user.Id)),
		Audience:  user.Name,
		ExpiresAt: time.Now().Add(time.Hour * 720).Unix(), //1 month
	})

	token, err := claims.SignedString([]byte(SecretKey))

	if err != nil {
		c.Status(fiber.StatusInternalServerError)
		return c.JSON(fiber.Map{
			"message": "could not login",
		})
	}

	// cookie := fiber.Cookie{
	// 	Name:     "jwt",
	// 	Value:    token,
	// 	Expires:  time.Now().Add(time.Minute * 1),
	// 	HTTPOnly: true,
	// }

	// c.Cookie(&cookie)

	return c.JSON(fiber.Map{
		"message": "success",
		"token":   token,
		"user":    user,
	})
}

func User(c *fiber.Ctx) error {

	token_string := c.Get("Authorization")

	if token_string == "" {
		return c.JSON(fiber.Map{
			"message": "Auth Token must be set",
		})
	}

	// check the token is contain bearer or not
	if strings.Contains(token_string, "Bearer") {
		splitToken := strings.Split(token_string, "Bearer ")
		token_string = splitToken[1]
	} else {
		return c.Status(400).JSON(fiber.Map{
			"message": "Token must start with Bearer",
		})
	}

	token, err := jwt.ParseWithClaims(token_string, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(SecretKey), nil
	})

	if err != nil {
		c.Status(fiber.StatusUnauthorized)
		return c.JSON(fiber.Map{
			"message": "unauthenticated",
		})
	}

	claims := token.Claims.(*jwt.StandardClaims)

	var user models.User
	var RolesDetails []models.RolesDetails

	user_info := database.DB.Where("id = ?", claims.Issuer).First(&user)
	if user_info.Error != nil {
		c.Status(400)
		return c.JSON(fiber.Map{
			"message": "id tidak ditemukkan",
		})
	}

	role_details := database.DB.Where("roles_id = ?", user.RoleId).Find(&RolesDetails)
	if role_details.Error != nil {
		c.Status(400)
		return c.JSON(fiber.Map{
			"message": "role_tidak ditemukan",
		})
	}

	return c.Status(200).JSON(fiber.Map{
		"message":      "success",
		"user":         user,
		"role_details": RolesDetails,
	})

	// return c.JSON(user)
}

func UserAuth(c *fiber.Ctx) error {

	token_string := c.Get("Authorization")
	if token_string == "" {
		return c.JSON(fiber.Map{
			"message": "Auth Token must be set",
		})
	}

	// check the token is contain bearer or not
	if strings.Contains(token_string, "Bearer") {
		splitToken := strings.Split(token_string, "Bearer ")
		token_string = splitToken[1]
	} else {
		return c.Status(400).JSON(fiber.Map{
			"message": "Token must start with Bearer",
		})
	}
	token, err := jwt.ParseWithClaims(token_string, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(SecretKey), nil
	})

	if err != nil {
		c.Status(fiber.StatusUnauthorized)
		return c.JSON(fiber.Map{
			"message": "unauthenticated",
		})
	}

	claims := token.Claims.(*jwt.StandardClaims)

	var user models.User

	database.DB.Where("id = ?", claims.Issuer).First(&user)

	return c.Next()
}

func Logout(c *fiber.Ctx) error {
	cookie := fiber.Cookie{
		Name:     "jwt",
		Value:    "",
		Expires:  time.Now().Add(-time.Hour),
		HTTPOnly: true,
	}

	c.Cookie(&cookie)

	return c.JSON(fiber.Map{
		"message": "success",
	})
}

func Test(c *fiber.Ctx) error {
	return c.SendString("Ini test")
}

func DeleteUser(c *fiber.Ctx) error {

	id := c.Params("id")
	if id == "" {
		return c.JSON(fiber.Map{
			"message": "id must be set",
		})
	}
	id_int, err := strconv.Atoi(id)

	if err != nil {
		return err
	}

	var user models.User

	result := database.DB.Delete(&user, id_int)

	if result.RowsAffected == 0 {
		return c.SendStatus(404)
	}

	return c.Status(200).JSON(fiber.Map{
		"message": "success delete user, id :" + id,
	})
}

func GetUserById(c *fiber.Ctx) error {

	var user models.User

	id := c.Params("id")
	if id == "" {
		return c.JSON(fiber.Map{
			"message": "id must be set",
		})
	}
	id_int, err := strconv.Atoi(id)

	if err != nil {
		return err
	}

	result := database.DB.Find(&user, id_int)

	if result.RowsAffected == 0 {
		return c.SendStatus(404)
	}

	return c.Status(200).JSON(fiber.Map{
		"message": "success",
		"data":    user,
	})
}

func UpdateUser(c *fiber.Ctx) error {
	var data map[string]string

	if err := c.BodyParser(&data); err != nil {
		return c.Status(400).JSON(err.Error())
	}

	id := c.Params("id")
	if id == "" {
		return c.JSON(fiber.Map{
			"message": "id must be set",
		})
	}

	id_int, err := strconv.Atoi(id)

	if err != nil {
		return err
	}

	password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14)

	user := models.User{
		Name:      data["name"],
		Email:     data["email"],
		Password:  password,
		RoleId:    data["role_id"],
		UpdatedAt: time.Now(),
	}

	result := database.DB.Where("id = ?", id_int).Updates(&user)

	if result.RowsAffected == 0 {
		return c.SendStatus(404)
	}

	return c.Status(200).JSON(fiber.Map{
		"message": "success update user, id :" + id,
		"data":    user,
	})
}

func UserAjaxList(c *fiber.Ctx) error {

	filterAjax := new(FilterAjaxUser)

	if err := c.QueryParser(filterAjax); err != nil {
		return c.Status(400).JSON(err.Error())
	}

	page, err := strconv.Atoi(filterAjax.Page)
	if err != nil {
		page = 1
	}
	perPage, err := strconv.Atoi(filterAjax.PerPage)
	if err != nil {
		perPage = 10
	}

	user := models.User{
		Name: filterAjax.Name,
	}

	var sort map[string]string
	sort = make(map[string]string)
	if filterAjax.Sort != "" {
		if filterAjax.SortDir == "" {
			sort[filterAjax.Sort] = "desc"
		} else {
			sort[filterAjax.Sort] = filterAjax.SortDir
		}
	}
	limit := perPage
	offset := (page - 1) * limit

	data, total, err := UserQueryAjaxList(user, limit, offset, sort)

	if err != nil {
		return c.Status(400).JSON(err.Error())
	}
	pages := math.Ceil(float64(*total) / float64(perPage))
	meta := datatable.MetaDataTable{
		CurrentPage: page,
		From:        offset + 1,
		LastPage:    int(pages),
		Path:        "",
		PerPage:     fmt.Sprintf("%v", limit),
		To:          offset + limit,
		Total:       int(*total),
	}

	datatableFormat := datatable.DataTable{
		Meta: datatable.MetaDataTable{
			CurrentPage: page,
			From:        0,
			LastPage:    1,
			Path:        *datatable.BuildUrlForLink(c.Request(), 0),
			PerPage:     strconv.Itoa(perPage),
			To:          0,
			Total:       0,
		},
		Data: nil,
		Links: datatable.LinkDatatable{
			First: *datatable.BuildUrlForLink(c.Request(), 1),
			Last:  nil,
			Next:  nil,
			Prev:  nil,
		},
		Payload: filterAjax,
	}

	if err == nil {
		var dataResponseUser []models.UserList

		for _, data := range data {
			dataResponseUser = append(dataResponseUser, data)
		}

		datatableFormat.Data = dataResponseUser
		meta.Path = *datatable.BuildUrlForLink(c.Request(), 0)
		datatableFormat.Meta = meta
		datatableFormat.Links = datatable.BuildLinksData(c.Request(), meta)
	}

	return c.JSON(fiber.Map{
		"message": "success",
		"data":    datatableFormat,
	})
}

func UserQueryAjaxList(filter models.User, limit, offset int, sort map[string]string) ([]models.UserList, *int64, error) {
	var (
		result []models.User
		count  int64
	)

	type res struct {
		Id    int64
		Name  string
		Email string
		Role  string
	}

	var results []models.UserList

	// query := database.DB.Where(filter)
	query := database.DB.Table("users").Select("users.id, users.name, users.email, roles.role").Joins("left join roles on users.role_id = roles.id").Scan(&results)
	fmt.Println(results)
	if sort == nil {
		query = query.Order("id")
	} else {
		for key, val := range sort {
			query = query.Order(fmt.Sprintf("%s %s", key, val))
		}
	}
	query = query.Offset(offset).Limit(limit).Find(&results)
	fmt.Println(result)
	if query.Error != nil {
		return nil, nil, query.Error
	}
	dbCount := database.DB.Table("users").Where(filter).Select("id").Count(&count)
	if dbCount.Error != nil {
		return nil, nil, dbCount.Error
	}
	return results, &count, nil

}

func GetNamaKasir(token_string string) (err error, user_id string, user_name string) {

	if token_string == "" {
		return
	}

	splitToken := strings.Split(token_string, "Bearer ")
	token_string = splitToken[1]

	token, err := jwt.ParseWithClaims(token_string, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(SecretKey), nil
	})

	if err != nil {
		return
	}

	claims := token.Claims.(*jwt.StandardClaims)

	var user models.User

	database.DB.Where("id = ?", claims.Issuer).First(&user)
	user_id = claims.Issuer
	user_name = claims.Audience
	fmt.Println("nama kasir :" + user_name)
	// fmt.Println("id kasir :" + user_id)

	return

	// return c.Status(200).JSON(fiber.Map{
	// 	"message": "success",
	// 	"user":    user,
	// })

	// return c.JSON(user)
}
