package main

import (
	json2 "encoding/json"
	"maps"
	"net/http"
	"slices"
	"time"

	"go-socket/Enums/ConnectionState"

	"github.com/google/uuid"
)

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

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

	targetConnection, err := ConvertStringUuid(request.FormValue("connectionid"))
	if err != nil {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}
	conn, ok := CacheGetConnection(user, targetConnection)
	if !ok {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}

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

	var target *User

	if user.Id == conn.RequestorId {
		target, err = GetUserById(ctx, conn.RecipientId)
	} else if user.Id == conn.RecipientId {
		target, err = GetUserById(ctx, conn.RequestorId)
	} else {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}

	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}
	message := &Message{
		Id:        uuid.New(),
		Content:   msgContent,
		CreatedAt: time.Now(),
		Sender:    user.Id,
		Receiver:  conn.Id,
	}

	WsMessageSendToUser(target, message)

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

	response.WriteHeader(http.StatusAccepted)
}

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

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

	before, err := ConvertStringTimestamp(request.FormValue("before"))
	if err != nil {
		before = time.Now()
	}

	messagesCap, err := ConvertStringUint32(request.FormValue("messages"))
	if err != nil {
		messagesCap = MaxDirectMsgCache
	}

	conn, ok := CacheGetConnection(user, connectionId)
	if !ok {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}

	buffer, bufferSize := conn.GetSortedMessagesBuff()

	var validBufCount uint32
	for validBufCount < bufferSize && buffer[validBufCount].CreatedAt.Before(before) {
		validBufCount++
	}

	var messages []*Message

	if validBufCount >= messagesCap {
		start := validBufCount - messagesCap
		messages = make([]*Message, messagesCap)
		for i := uint32(0); i < messagesCap; i++ {
			messages[i] = buffer[start+i]
		}
	} else {
		remaining := messagesCap - validBufCount
		cutoff := before
		if validBufCount > 0 {
			cutoff = buffer[0].CreatedAt
		}
		dbMessages, err := DbConnectionGetMessagesBefore(ctx, cutoff, connectionId, remaining)
		if err != nil {
			http.Error(response, "internal server error", http.StatusInternalServerError)
			return
		}
		messages = make([]*Message, 0, uint32(len(dbMessages))+validBufCount)
		messages = append(messages, dbMessages...)
		for i := uint32(0); i < validBufCount; i++ {
			messages = append(messages, buffer[i])
		}
	}

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

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

func HttpHandleUserNewConnection(response http.ResponseWriter, request *http.Request) {
	if !HttpMethodAllowed(&response, request) {
		return
	}
	ctx := request.Context()
	requestor, err := GetUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}
	recipientId, err := ConvertStringUint32(request.FormValue("recipient"))
	if err != nil {
		http.Error(response, "no such user", http.StatusUnauthorized)
		return
	}
	recipient, err := GetUserById(ctx, recipientId)
	if err != nil {
		http.Error(response, "no such user", http.StatusUnauthorized)
		return
	}

	if requestor.Id == recipient.Id {
		http.Error(response, "cannot connect to yourself", http.StatusBadRequest)
		return
	}

	requestor.Mu.RLock()
	for _, connection := range requestor.Connections {
		if (connection.RequestorId == requestor.Id && connection.RecipientId == recipient.Id) ||
			(connection.RecipientId == requestor.Id && connection.RequestorId == recipient.Id) {
			requestor.Mu.RUnlock()
			http.Error(response, "connection already exists", http.StatusBadRequest)
			return
		}
	}
	requestor.Mu.RUnlock()

	connection := &Connection{
		CreatedAt:   time.Now(),
		RequestorId: requestor.Id,
		RecipientId: recipient.Id,
		State:       ConnectionState.Stranger,
	}
	err = DbConnectionSave(ctx, connection)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}
	CacheAddConnection(requestor, recipient, connection)

	response.WriteHeader(http.StatusCreated)
	return
}

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

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

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

	conn, ok := CacheGetConnection(user, connectionId)
	if !ok {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}

	var user2 *User
	if conn.RequestorId == user.Id {
		recipient, err := GetUserById(ctx, conn.RecipientId)
		if err != nil {
			http.Error(response, "internal server error", http.StatusInternalServerError)
			return
		}
		user2 = recipient
	} else if conn.RecipientId == user.Id {
		requestor, err := GetUserById(ctx, conn.RequestorId)
		if err != nil {
			http.Error(response, "internal server error", http.StatusInternalServerError)
			return
		}
		user2 = requestor
	} else {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}

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

	CacheDeleteConnection(user, user2, connectionId)

	response.WriteHeader(http.StatusAccepted)
}

func HttpHandleUserElevateConnection(response http.ResponseWriter, request *http.Request) {
	if !HttpMethodAllowed(&response, request) {
		return
	}
	ctx := request.Context()
	user, err := GetUserByToken(ctx, request.FormValue("token"))
	if err != nil {
		http.Error(response, "invalid token", http.StatusUnauthorized)
		return
	}
	connectionId, err := ConvertStringUuid(request.FormValue("connectionid"))
	if err != nil {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}
	conn, ok := CacheGetConnection(user, connectionId)
	if !ok {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}

	if conn.RecipientId != user.Id {
		http.Error(response, "invalid connectionid", http.StatusBadRequest)
		return
	}

	switch conn.State {
	case ConnectionState.Stranger:
		conn.State = ConnectionState.Friend
		break
	case ConnectionState.GroupFellow:
		conn.State = ConnectionState.Stranger
		break
	default:
		http.Error(response, "cannot elevate further", http.StatusBadRequest)
		return
	}

	err = DbConnectionUpdateState(ctx, conn)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}
	response.WriteHeader(http.StatusAccepted)
}

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

	user.Mu.RLock()
	connections := slices.Collect(maps.Values(user.Connections))
	user.Mu.RUnlock()
	json, err := json2.Marshal(connections)
	if err != nil {
		http.Error(response, "internal server error", http.StatusInternalServerError)
		return
	}

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