package graph

import (
	"context"
	"errors"
	"github.com/Unkn0wnCat/matrix-veles/graph/model"
	"github.com/Unkn0wnCat/matrix-veles/internal/db"
	model2 "github.com/Unkn0wnCat/matrix-veles/internal/db/model"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"log"
	"time"
)

func PerformListMaintainerCheck(listIdHex string, userIdHex string) error {
	id, err := primitive.ObjectIDFromHex(listIdHex)
	if err != nil {
		return err
	}

	list, err := db.GetListByID(id)
	if err != nil {
		return err
	}

	if list.Creator.Hex() == userIdHex {
		return nil
	}

	for _, maintainerId := range list.Maintainers {
		if maintainerId.Hex() == userIdHex {
			return nil
		}
	}

	return errors.New("unauthorized")
}

func GetUserFromContext(ctx context.Context) (*model2.DBUser, error) {
	userID, err := GetUserIDFromContext(ctx)
	if err != nil {
		return nil, err
	}

	user, err := db.GetUserByID(*userID)
	if err != nil {
		return nil, errors.New("invalid session")
	}

	return user, nil
}

func GetUserIDFromContext(ctx context.Context) (*primitive.ObjectID, error) {
	claimsVal := ctx.Value("claims")
	var claims model2.JwtClaims
	if claimsVal != nil {
		claims = claimsVal.(model2.JwtClaims)
		if claims.Valid() == nil {
			sub := claims.Subject

			id, err := primitive.ObjectIDFromHex(sub)
			if err != nil {
				return nil, err
			}

			return &id, nil
		}
	}

	return nil, errors.New("no user")
}

func CheckOwnerConstraint(ctx context.Context, realOwnerIDHex string) error {
	constraint, ok := ctx.Value("ownerConstraint").(string)

	if !ok {
		return nil // This means no ownerConstraint is set
	}

	if constraint != realOwnerIDHex {
		return errors.New("owner constraint violation")
	}

	return nil
}

func buildStringFilter(filter *model.StringFilter) bson.M {
	compiledFilter := bson.M{}

	if filter != nil {
		if filter.Eq != nil {
			compiledFilter["$eq"] = *filter.Eq
		}
		if filter.Neq != nil {
			compiledFilter["$ne"] = *filter.Eq
		}
		if filter.Regex != nil {
			compiledFilter["$regex"] = *filter.Regex
		}
	}

	return compiledFilter
}

func buildTimestampFilter(filter *model.TimestampFilter) (*bson.M, error) {
	compiledFilter := bson.M{}

	if filter != nil {
		if filter.After != nil {
			compiledFilter["$gt"] = *filter.After
		}
		if filter.Before != nil {
			compiledFilter["$lt"] = *filter.Before
		}
	}

	return &compiledFilter, nil
}

func buildIntFilter(filter *model.IntFilter) bson.M {
	compiledFilter := bson.M{}

	if filter != nil {
		if filter.Eq != nil {
			compiledFilter["$eq"] = *filter.Eq
		}
		if filter.Neq != nil {
			compiledFilter["$ne"] = *filter.Neq
		}
		if filter.Gt != nil {
			compiledFilter["$gt"] = *filter.Gt
		}
		if filter.Lt != nil {
			compiledFilter["$lt"] = *filter.Lt
		}
	}

	return compiledFilter
}

func buildStringArrayFilter(filter *model.StringArrayFilter) bson.M {
	compiledFilter := bson.M{}

	if filter != nil {
		if filter.ElemMatch != nil {
			compiledFilter["$elemMatch"] = buildStringFilter(filter.ElemMatch)
		}
		if filter.ContainsAll != nil {
			compiledFilter["$all"] = filter.ContainsAll
		}
		if filter.Length != nil {
			compiledFilter["$size"] = *filter.Length
		}
	}

	return compiledFilter
}

func idArrayToPrimitiveID(ids []*string) ([]primitive.ObjectID, error) {
	var pIds []primitive.ObjectID

	for _, id := range ids {
		pId, err := primitive.ObjectIDFromHex(*id)
		if err != nil {
			return nil, err
		}

		pIds = append(pIds, pId)
	}

	return pIds, nil
}

func buildIDArrayFilter(filter *model.IDArrayFilter) (bson.M, error) {
	compiledFilter := bson.M{}
	var err error

	if filter != nil {
		if filter.ContainsAll != nil {
			compiledFilter["$all"], err = idArrayToPrimitiveID(filter.ContainsAll)
			if err != nil {
				return nil, err
			}
		}
		if filter.Length != nil {
			compiledFilter["$size"] = *filter.Length
		}
	}

	return compiledFilter, nil
}

func buildSortRule(sort *model.SortRule) interface{} {
	if sort.Direction == "ASC" {
		return 1
	}

	return -1
}

func buildDBUserFilter(first *int, after *string, filter *model.UserFilter, sort *model.UserSort) (*bson.M, *bson.M, *int64, error) {
	compiledFilter := bson.M{}
	compiledSort := bson.M{}

	var filterBson *bson.M
	var cursorBson *bson.M
	limit := 25

	if sort != nil {
		if sort.ID != nil {
			compiledSort["_id"] = buildSortRule(sort.ID)
		}
		if sort.Username != nil {
			compiledSort["username"] = buildSortRule(sort.Username)
		}
		if sort.Admin != nil {
			compiledSort["admin"] = buildSortRule(sort.Admin)
		}
	}

	if first != nil {
		limit = *first
	}

	if after != nil {
		cursorBsonW := bson.M{}

		afterID, err := primitive.ObjectIDFromHex(*after)
		if err != nil {
			return nil, nil, nil, err
		}

		cursorBsonW["_id"] = bson.M{"$gt": afterID}

		cursorBson = &cursorBsonW
	}

	if filter != nil {
		filterBsonW := bson.M{}

		if filter.ID != nil {
			filterBsonW["_id"] = *filter.ID
		}

		if filter.Username != nil {
			filterBsonW["username"] = buildStringFilter(filter.Username)
		}

		if filter.Admin != nil {
			filterBsonW["admin"] = *filter.Admin
		}

		if filter.MatrixLinks != nil {
			filterBsonW["matrix_links"] = buildStringArrayFilter(filter.MatrixLinks)
		}

		if filter.PendingMatrixLinks != nil {
			filterBsonW["pending_matrix_links"] = buildStringArrayFilter(filter.PendingMatrixLinks)
		}

		filterBson = &filterBsonW
	}

	if filterBson != nil && cursorBson != nil {
		compiledFilter["$and"] = bson.A{*cursorBson, *filterBson}
	}

	if filterBson == nil && cursorBson != nil {
		compiledFilter = *cursorBson
	}

	if filterBson != nil && cursorBson == nil {
		compiledFilter = *filterBson
	}

	convLimit := int64(limit)

	return &compiledFilter, &compiledSort, &convLimit, nil
}

func buildDBListFilter(first *int, after *string, filter *model.ListFilter, sort *model.ListSort) (*bson.M, *bson.M, *int64, error) {
	compiledFilter := bson.M{}
	compiledSort := bson.M{}

	var filterBson *bson.M
	var cursorBson *bson.M
	limit := 25

	var err error

	if sort != nil {
		if sort.ID != nil {
			compiledSort["_id"] = buildSortRule(sort.ID)
		}
		if sort.Name != nil {
			compiledSort["name"] = buildSortRule(sort.Name)
		}
	}

	if first != nil {
		limit = *first
	}

	if after != nil {
		cursorBsonW := bson.M{}

		afterID, err := primitive.ObjectIDFromHex(*after)
		if err != nil {
			return nil, nil, nil, err
		}

		cursorBsonW["_id"] = bson.M{"$gt": afterID}

		cursorBson = &cursorBsonW
	}

	if filter != nil {
		filterBsonW := bson.M{}

		if filter.ID != nil {
			filterBsonW["_id"] = *filter.ID
		}

		if filter.Name != nil {
			filterBsonW["name"] = buildStringFilter(filter.Name)
		}

		if filter.Tags != nil {
			filterBsonW["tags"] = buildStringArrayFilter(filter.Tags)
		}

		if filter.Maintainers != nil {
			filterBsonW["maintainers"], err = buildIDArrayFilter(filter.Maintainers)
			if err != nil {
				return nil, nil, nil, err
			}
		}

		filterBson = &filterBsonW
	}

	if filterBson != nil && cursorBson != nil {
		compiledFilter["$and"] = bson.A{*cursorBson, *filterBson}
	}

	if filterBson == nil && cursorBson != nil {
		compiledFilter = *cursorBson
	}

	if filterBson != nil && cursorBson == nil {
		compiledFilter = *filterBson
	}

	convLimit := int64(limit)

	return &compiledFilter, &compiledSort, &convLimit, nil
}

func buildDBEntryFilter(first *int, after *string, filter *model.EntryFilter, sort *model.EntrySort) (*bson.M, *bson.M, *int64, error) {
	compiledFilter := bson.M{}
	compiledSort := bson.M{}

	var filterBson *bson.M
	var cursorBson *bson.M
	limit := 25

	var err error

	if sort != nil {
		if sort.ID != nil {
			compiledSort["_id"] = buildSortRule(sort.ID)
		}
		if sort.Timestamp != nil {
			compiledSort["timestamp"] = buildSortRule(sort.Timestamp)
		}
		if sort.AddedBy != nil {
			compiledSort["added_by"] = buildSortRule(sort.AddedBy)
		}
		if sort.HashValue != nil {
			compiledSort["hash_value"] = buildSortRule(sort.HashValue)
		}
	}

	if first != nil {
		limit = *first
	}

	if after != nil {
		cursorBsonW := bson.M{}

		afterID, err := primitive.ObjectIDFromHex(*after)
		if err != nil {
			return nil, nil, nil, err
		}

		cursorBsonW["_id"] = bson.M{"$gt": afterID}

		cursorBson = &cursorBsonW
	}

	if filter != nil {
		filterBsonW := bson.M{}

		if filter.ID != nil {
			filterBsonW["_id"] = *filter.ID
		}

		if filter.HashValue != nil {
			filterBsonW["hash_value"] = buildStringFilter(filter.HashValue)
		}

		if filter.Tags != nil {
			filterBsonW["tags"] = buildStringArrayFilter(filter.Tags)
		}

		if filter.AddedBy != nil {
			dbId, err := primitive.ObjectIDFromHex(*filter.AddedBy)
			if err != nil {
				return nil, nil, nil, err
			}

			filterBsonW["added_by"] = dbId
		}

		if filter.Timestamp != nil {
			filterBsonW["timestamp"], err = buildTimestampFilter(filter.Timestamp)
			if err != nil {
				return nil, nil, nil, err
			}
		}

		/*if filter.FileURL != nil {
			filterBsonW["file_url"] = buildStringFilter(filter.FileURL)
		}*/

		if filter.PartOf != nil {
			filterBsonW["part_of"], err = buildIDArrayFilter(filter.PartOf)
			if err != nil {
				return nil, nil, nil, err
			}
		}

		filterBson = &filterBsonW
	}

	if filterBson != nil && cursorBson != nil {
		compiledFilter["$and"] = bson.A{*cursorBson, *filterBson}
	}

	if filterBson == nil && cursorBson != nil {
		compiledFilter = *cursorBson
	}

	if filterBson != nil && cursorBson == nil {
		compiledFilter = *filterBson
	}

	convLimit := int64(limit)

	return &compiledFilter, &compiledSort, &convLimit, nil
}

func ResolveComments(comments []*model2.DBComment, first *int, after *string) (*model.CommentConnection, error) {
	if len(comments) == 0 {
		return nil, nil
	}

	startIndex := 0

	if after != nil {
		afterTs, err := time.Parse(time.RFC3339Nano, *after)
		if err != nil {
			return nil, err
		}

		set := false

		for i, comment := range comments {
			if afterTs.Before(comment.Timestamp) {
				startIndex = i
				set = true
				break
			}
		}

		if !set {
			return nil, nil
		}
	}

	if startIndex >= len(comments) {
		return nil, nil
	}

	comments = comments[startIndex:]

	length := 25

	if first != nil {
		length = *first
	}

	cut := false

	if len(comments) > length {
		cut = true
		comments = comments[:length]
	}

	var edges []*model.CommentEdge

	for _, comment := range comments {
		edges = append(edges, &model.CommentEdge{
			Node:   model.MakeComment(comment),
			Cursor: comment.Timestamp.Format(time.RFC3339Nano),
		})
	}
	log.Println(edges)

	return &model.CommentConnection{
		PageInfo: &model.PageInfo{
			HasPreviousPage: startIndex > 0,
			HasNextPage:     cut,
			StartCursor:     edges[0].Cursor,
			EndCursor:       edges[len(edges)-1].Cursor,
		},
		Edges: nil,
	}, nil
}

func buildDBRoomFilter(first *int, after *string, filter *model.RoomFilter /*, sort *model.UserSort*/, userMxids []string) (*bson.M, *bson.M, *int64, error) {
	compiledFilter := bson.M{}
	compiledSort := bson.M{}

	var filterBson *bson.M
	var cursorBson *bson.M
	limit := 25

	/*if sort != nil {
		if sort.ID != nil {
			compiledSort["_id"] = buildSortRule(sort.ID)
		}
		if sort.Username != nil {
			compiledSort["username"] = buildSortRule(sort.Username)
		}
		if sort.Admin != nil {
			compiledSort["admin"] = buildSortRule(sort.Admin)
		}
	}*/

	if first != nil {
		limit = *first
	}

	if after != nil {
		cursorBsonW := bson.M{}

		afterID, err := primitive.ObjectIDFromHex(*after)
		if err != nil {
			return nil, nil, nil, err
		}

		cursorBsonW["_id"] = bson.M{"$gt": afterID}

		cursorBson = &cursorBsonW
	}

	if filter != nil {
		filterBsonW := bson.M{}

		if filter.ID != nil {
			filterBsonW["_id"] = *filter.ID
		}

		if filter.Debug != nil {
			filterBsonW["debug"] = *filter.Debug
		}

		if filter.Active != nil {
			filterBsonW["active"] = *filter.Active
		}

		if filter.CanEdit != nil && *filter.CanEdit == true {
			filterBsonW["admins"] = bson.M{
				"$in": userMxids,
			}
		}

		filterBson = &filterBsonW
	}

	if filterBson != nil && cursorBson != nil {
		compiledFilter["$and"] = bson.A{*cursorBson, *filterBson}
	}

	if filterBson == nil && cursorBson != nil {
		compiledFilter = *cursorBson
	}

	if filterBson != nil && cursorBson == nil {
		compiledFilter = *filterBson
	}

	convLimit := int64(limit)

	return &compiledFilter, &compiledSort, &convLimit, nil
}