package main

import (
	"context"
	json2 "encoding/json"
	"errors"
	"fmt"
	"maps"
	"net/http"
	"slices"
	"strconv"
	"strings"
	"time"

	"golang.org/x/crypto/bcrypt"
)

func isMethodAllowed(response *http.ResponseWriter, request *http.Request) bool {
	if request.Method != http.MethodPost {
		http.Error(*response, "POST only", http.StatusMethodNotAllowed)
		return false
	}
	return true
}

func getUserById(ctx context.Context, userId uint32) (*User, error) {
	user, err := CacheGetUserById(userId)
	if err != nil {
		user = &User{Id: userId}
		if err = DbUserGetById(ctx, user); err != nil {
			return nil, err
		}
		if err = DbUserGetGroups(ctx, user); err != nil {
			return nil, err
		}
		if err = DbConnectionsGetBelongingToUser(ctx, user); err != nil {
			return nil, err
		}
		CacheSaveUser(user)
	}

	return user, nil
}

func getUserByToken(ctx context.Context, token string) (*User, error) {
	userId, err := TokenValidateGetId(token)
	if err != nil {
		return nil, err
	}
	return getUserById(ctx, userId)
}

func getGroup(ctx context.Context, groupId uint32) (*Group, error) {
	group, err := CacheGetGroup(groupId)
	if err != nil {
		group = &Group{Id: groupId}
		if err = DbGroupGetById(ctx, group); err != nil {
			return nil, err
		}
		if err = DbGroupGetMembers(ctx, group); err != nil {
			return nil, err
		}
		CacheSaveGroup(group)
	}
	return group, nil
}

func isOwner(user *User, group *Group) bool {
	if group.OwnerId == user.Id {
		return true
	}
	return false
}

func getIfOwnerUserAndGroup(ctx context.Context, response *http.ResponseWriter, request *http.Request) (*User, *Group, error) {
	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(*response, "invalid token", http.StatusUnauthorized)
		return nil, nil, err
	}

	affectedGroupId, err := ConvertStringUint32(request.FormValue("groupid"))
	if err != nil {
		http.Error(*response, "no such group", http.StatusUnauthorized)
		return nil, nil, err
	}

	group, err := getGroup(ctx, affectedGroupId)
	if err != nil {
		http.Error(*response, "no such group", http.StatusUnauthorized)
		return nil, nil, err
	}

	if !isOwner(user, group) {
		http.Error(*response, "no such group", http.StatusUnauthorized)
		return nil, nil, errors.New("not an owner")
	}
	return user, group, nil
}

func HttpHandleUserNew(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	username := request.FormValue("username")
	if len(username) < 4 {
		http.Error(response, "no or short username", http.StatusBadRequest)
		return
	}

	password := request.FormValue("password")
	if len(password) < 8 {
		http.Error(response, "no or short password", http.StatusBadRequest)
		return
	}

	color, err := ConvertStringToRgb(request.FormValue("color"))
	if err != nil {
		http.Error(response, "bad color", http.StatusBadRequest)
		return
	}

	hashedPassword, err := PasswordHash(password)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	newUser := &User{
		Name:         username,
		PasswordHash: hashedPassword,
		Color:        color,
		CreatedAt:    time.Now(),
	}

	ctx := request.Context()

	err = DbUserSave(ctx, newUser)
	if err != nil {
		http.Error(response, "name taken", http.StatusUnauthorized)
		return
	}

	response.WriteHeader(http.StatusCreated)
}

func HttpHandleUserDelete(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}
	ctx := request.Context()

	userId, err := TokenValidateGetId(request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}

	err = DbUserDelete(ctx, userId)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	CacheDeleteUser(userId)
	response.WriteHeader(http.StatusAccepted)
}

// HttpHandleUserModifyAppearance currently just color
func HttpHandleUserModifyAppearance(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}

	color, err := ConvertStringToRgb(request.FormValue("color"))
	if err != nil {
		http.Error(response, "invalid color", http.StatusBadRequest)
		return
	}
	user.Color = color
	err = DbUserSetColor(ctx, user)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}
	response.WriteHeader(http.StatusAccepted)
}

// HttpHandleUserModifyAbout currently just pronouns
func HttpHandleUserModifyAbout(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}

	pronouns := request.FormValue("pronouns")
	if len(pronouns) > 25 || len(pronouns) < 2 {
		http.Error(response, "invalid pronouns", http.StatusBadRequest)
		return
	}

	user.Pronouns = pronouns
	err = DbUserSetPronouns(ctx, user)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}
	response.WriteHeader(http.StatusAccepted)
}

func HttpHandleUserMessage(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}

}

func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}
}

func HttpHandleUserDeleteConnection(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}
	ctx := request.Context()

	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}
}

func HttpHandleUserAcceptConnection(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}
	ctx := request.Context()

	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}
}

func HttpHandleUserGetConnections(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}
	ctx := request.Context()
	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}
}

func HttpHandleTokenNew(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	username := request.FormValue("username")
	if len(username) < 4 {
		http.Error(response, "no or short username", http.StatusBadRequest)
		return
	}

	password := request.FormValue("password")

	if len(password) < 8 {
		http.Error(response, "no or short password", http.StatusBadRequest)
		return
	}

	var (
		user *User
		err  error
		ctx  = request.Context()
	)

	user, err = CacheGetUserByName(username)
	if err != nil {
		user = &User{Name: username}
		if err = DbUserGetByName(ctx, user); err != nil {
			http.Error(response, "bad login", http.StatusUnauthorized)
			return
		}
		if err = DbUserGetGroups(ctx, user); err != nil {
			http.Error(response, "bad login", http.StatusUnauthorized)
			return
		}
		if err = DbUserGetConnections(ctx, user); err != nil {
			http.Error(response, "bad login", http.StatusUnauthorized)
			return
		}
		CacheSaveUser(user)
	}

	err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
	if err != nil {
		http.Error(response, "bad login", http.StatusUnauthorized)
		return
	}

	token, err := TokenCreate(user.Id)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	json, err := json2.Marshal(LoginReturn{Token: token, UserId: user.Id})

	response.WriteHeader(http.StatusCreated)
	response.Write([]byte(json))
}

func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()

	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}

	name := request.FormValue("name")
	if name == "" {
		name = "Best group ever"
	}

	colorString := request.FormValue("color")
	color, err := ConvertStringToRgb(colorString)
	if err != nil {
		http.Error(response, "invalid color", http.StatusBadRequest)
		return
	}

	group := Group{
		Name:      name,
		CreatedAt: time.Now(),
		OwnerId:   user.Id,
		CreatorId: user.Id,
		Color:     color,
		Users:     map[uint32]struct{}{user.Id: {}},
	}

	enableUserColors := request.FormValue("enableUserColors")
	if enableUserColors == "1" {
		group.EnableUserColors = true
	}

	err = DbGroupSave(ctx, &group)
	if err != nil {
		http.Error(response, err.Error(), http.StatusInternalServerError)
		return
	}
	response.WriteHeader(http.StatusCreated)
	fmt.Fprintf(response, "%d", group.Id)
}

func HttpHandleGroupDelete(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
	if err != nil {
		return
	}

	err = DbGroupDelete(ctx, group)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	response.WriteHeader(http.StatusAccepted)
}

func HttpHandleGroupAddUsers(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()

	_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
	if err != nil {
		return
	}

	usersString := request.FormValue("users")
	var remainingUsersCount = int(MaxUsersInGroup) - len(group.Users)
	if remainingUsersCount < 1 {
		http.Error(response, "max users", http.StatusUnauthorized)
		return
	}

	usersStringSlice := strings.SplitN(usersString, ",", remainingUsersCount+1)
	if len(usersStringSlice) == 0 {
		http.Error(response, "no users to add", http.StatusBadRequest)
		return
	}

	var ids [MaxUsersInGroup]uint32
	var idx uint32 = 0
	for _, s := range usersStringSlice {
		if idx >= MaxUsersInGroup {
			break
		}
		id, err := ConvertStringUint32(strings.TrimSpace(s))
		if err != nil {
			continue
		}
		ids[idx] = id
		idx++
	}
	if idx == 0 {
		http.Error(response, "no valid users", http.StatusBadRequest)
		return
	}

	err = DbGroupAddUsers(ctx, group.Id, &ids)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	for i := uint32(0); i < idx; i++ {
		group.Users[ids[i]] = struct{}{}
	}

	response.WriteHeader(http.StatusAccepted)
}

func HttpHandleGroupRemoveUser(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()

	_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
	if err != nil {
		return
	}

	usersString := request.FormValue("users")

	usersStringSlice := strings.SplitN(usersString, ",", int(MaxUsersInGroup)+1)

	var ids [MaxUsersInGroup]uint32
	var idx uint32 = 0
	for _, s := range usersStringSlice {
		if idx >= MaxUsersInGroup {
			break
		}
		id, err := ConvertStringUint32(strings.TrimSpace(s))
		if err != nil {
			continue
		}
		ids[idx] = id
		idx++
	}
	if idx == 0 {
		http.Error(response, "no valid users", http.StatusBadRequest)
		return
	}

	count, err := DbGroupRemoveUsers(ctx, group.Id, &ids)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	for i := uint32(0); i < idx; i++ {
		delete(group.Users, ids[i])
	}

	response.WriteHeader(http.StatusAccepted)
	response.Write([]byte(strconv.Itoa(count)))
}

func HttpHandleGroupChangeColor(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
	if err != nil {
		return
	}

	color, err := ConvertStringToRgb(request.FormValue("color"))
	if err != nil {
		http.Error(response, "invalid color", http.StatusBadRequest)
		return
	}

	group.Color = color
	err = DbGroupSetColor(ctx, group)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	response.WriteHeader(http.StatusAccepted)
	response.Write([]byte("changed"))
}

func HttpHandleGroupChangeOwner(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	_, group, err := getIfOwnerUserAndGroup(ctx, &response, request)
	if err != nil {
		return
	}

	newOwnerName := request.FormValue("newOwner")

	newOwner, err := CacheGetUserByName(newOwnerName)
	if err != nil {
		newOwner = &User{Name: newOwnerName}
		err = DbUserGetByName(ctx, newOwner)
		if err != nil {
			http.Error(response, "user not in group", http.StatusBadRequest)
			return
		}

		CacheSaveUser(newOwner)
	}

	_, ok := group.Users[newOwner.Id]
	if !ok {
		http.Error(response, "user not in group", http.StatusBadRequest)
		return
	}

	group.OwnerId = newOwner.Id
	err = DbGroupSetOwnerId(ctx, group)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	response.WriteHeader(http.StatusAccepted)
}

func HttpHandleGroupMessage(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()

	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusBadRequest)
		return
	}
	groupIdStr := request.FormValue("groupid")
	groupId, err := ConvertStringUint32(groupIdStr)
	if err != nil {
		http.Error(response, "no such group", http.StatusUnauthorized)
		return
	}

	group, err := getGroup(ctx, groupId)
	if err != nil {
		http.Error(response, "no such group", http.StatusUnauthorized)
		return
	}

	content := request.FormValue("content")
	if content == "" {
		http.Error(response, "empty message", http.StatusBadRequest)
		return
	}

	_, ok := group.Users[user.Id]
	if !ok {
		http.Error(response, "no such group", http.StatusUnauthorized)
		return
	}

	err = WsSendToGroup(group, user, content)
	if err != nil {
		http.Error(response, err.Error(), http.StatusBadRequest)
		return
	}
	response.WriteHeader(http.StatusAccepted)
}

func HttpHandleGroupsGetWithoutMembers(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()

	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}

	groups := make(map[uint32]*Group, len(user.Groups))
	for groupId := range user.Groups {
		group, err := getGroup(ctx, groupId)
		if err != nil {
			continue
		}
		groups[groupId] = group
	}

	json, err := json2.Marshal(groups)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	response.WriteHeader(http.StatusAccepted)
	response.Write(json)
}

func HttpHandleGroupMembersGet(response http.ResponseWriter, request *http.Request) {
	if !isMethodAllowed(&response, request) {
		return
	}

	ctx := request.Context()
	user, err := getUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}

	groupStr := request.FormValue("group")
	groupId, err := ConvertStringUint32(groupStr)
	if err != nil {
		http.Error(response, "invalid group", http.StatusBadRequest)
		return
	}

	_, ok := user.Groups[groupId]
	if !ok {
		http.Error(response, "no such group", http.StatusUnauthorized)
		return
	}

	group, err := getGroup(ctx, groupId)
	if err != nil {
		http.Error(response, "no such group", http.StatusUnauthorized)
		return
	}

	groupMembers := slices.Collect(maps.Keys(group.Users))

	json, err := json2.Marshal(groupMembers)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

	response.WriteHeader(http.StatusAccepted)
	response.Write(json)
}
