// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package user

import (
	"context"

	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/container"
	"code.gitea.io/gitea/modules/timeutil"
	"code.gitea.io/gitea/modules/util"

	"xorm.io/builder"
)

var (
	ErrBlockOrganization = util.NewInvalidArgumentErrorf("cannot block an organization")
	ErrCanNotBlock       = util.NewInvalidArgumentErrorf("cannot block the user")
	ErrCanNotUnblock     = util.NewInvalidArgumentErrorf("cannot unblock the user")
	ErrBlockedUser       = util.NewPermissionDeniedErrorf("user is blocked")
)

type Blocking struct {
	ID          int64 `xorm:"pk autoincr"`
	BlockerID   int64 `xorm:"UNIQUE(block)"`
	Blocker     *User `xorm:"-"`
	BlockeeID   int64 `xorm:"UNIQUE(block)"`
	Blockee     *User `xorm:"-"`
	Note        string
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

func (*Blocking) TableName() string {
	return "user_blocking"
}

func init() {
	db.RegisterModel(new(Blocking))
}

func UpdateBlockingNote(ctx context.Context, id int64, note string) error {
	_, err := db.GetEngine(ctx).ID(id).Cols("note").Update(&Blocking{Note: note})
	return err
}

func IsUserBlockedBy(ctx context.Context, blockee *User, blockerIDs ...int64) bool {
	if len(blockerIDs) == 0 {
		return false
	}

	if blockee.IsAdmin {
		return false
	}

	cond := builder.Eq{"user_blocking.blockee_id": blockee.ID}.
		And(builder.In("user_blocking.blocker_id", blockerIDs))

	has, _ := db.GetEngine(ctx).Where(cond).Exist(&Blocking{})
	return has
}

type FindBlockingOptions struct {
	db.ListOptions
	BlockerID int64
	BlockeeID int64
}

func (opts *FindBlockingOptions) ToConds() builder.Cond {
	cond := builder.NewCond()
	if opts.BlockerID != 0 {
		cond = cond.And(builder.Eq{"user_blocking.blocker_id": opts.BlockerID})
	}
	if opts.BlockeeID != 0 {
		cond = cond.And(builder.Eq{"user_blocking.blockee_id": opts.BlockeeID})
	}
	return cond
}

func FindBlockings(ctx context.Context, opts *FindBlockingOptions) ([]*Blocking, int64, error) {
	return db.FindAndCount[Blocking](ctx, opts)
}

func GetBlocking(ctx context.Context, blockerID, blockeeID int64) (*Blocking, error) {
	blocks, _, err := FindBlockings(ctx, &FindBlockingOptions{
		BlockerID: blockerID,
		BlockeeID: blockeeID,
	})
	if err != nil {
		return nil, err
	}
	if len(blocks) == 0 {
		return nil, nil
	}
	return blocks[0], nil
}

type BlockingList []*Blocking

func (blocks BlockingList) LoadAttributes(ctx context.Context) error {
	ids := make(container.Set[int64], len(blocks)*2)
	for _, b := range blocks {
		ids.Add(b.BlockerID)
		ids.Add(b.BlockeeID)
	}

	userList, err := GetUsersByIDs(ctx, ids.Values())
	if err != nil {
		return err
	}

	userMap := make(map[int64]*User, len(userList))
	for _, u := range userList {
		userMap[u.ID] = u
	}

	for _, b := range blocks {
		b.Blocker = userMap[b.BlockerID]
		b.Blockee = userMap[b.BlockeeID]
	}

	return nil
}