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

package project

import (
	"context"
	"errors"

	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/util"
)

// ProjectIssue saves relation from issue to a project
type ProjectIssue struct { //revive:disable-line:exported
	ID        int64 `xorm:"pk autoincr"`
	IssueID   int64 `xorm:"INDEX"`
	ProjectID int64 `xorm:"INDEX"`

	// ProjectColumnID should not be zero since 1.22. If it's zero, the issue will not be displayed on UI and it might result in errors.
	ProjectColumnID int64 `xorm:"'project_board_id' INDEX"`

	// the sorting order on the column
	Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
}

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

func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error {
	_, err := db.GetEngine(ctx).Where("project_id=?", projectID).Delete(&ProjectIssue{})
	return err
}

func IsIssueInColumn(ctx context.Context, issueID, projectID, columnID int64) (bool, error) {
	return db.GetEngine(ctx).Exist(&ProjectIssue{
		IssueID:         issueID,
		ProjectID:       projectID,
		ProjectColumnID: columnID,
	})
}

// GetColumnIssueNextSorting returns the sorting value to append an issue at the end of the column.
func GetColumnIssueNextSorting(ctx context.Context, projectID, columnID int64) (int64, error) {
	res := struct {
		MaxSorting int64
		IssueCount int64
	}{}
	if _, err := db.GetEngine(ctx).Select("max(sorting) AS max_sorting, count(*) AS issue_count").
		Table("project_issue").
		Where("project_id=?", projectID).
		And("project_board_id=?", columnID).
		Get(&res); err != nil {
		return 0, err
	}
	return util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0), nil
}

func moveIssuesToAnotherColumn(ctx context.Context, oldColumn, newColumn *Column) error {
	if oldColumn.ProjectID != newColumn.ProjectID {
		return errors.New("columns have to be in the same project")
	}

	if oldColumn.ID == newColumn.ID {
		return nil
	}

	movedIssues, err := oldColumn.GetIssues(ctx)
	if err != nil {
		return err
	}
	if len(movedIssues) == 0 {
		return nil
	}

	nextSorting, err := GetColumnIssueNextSorting(ctx, newColumn.ProjectID, newColumn.ID)
	if err != nil {
		return err
	}
	return db.WithTx(ctx, func(ctx context.Context) error {
		for i, issue := range movedIssues {
			issue.ProjectColumnID = newColumn.ID
			issue.Sorting = nextSorting + int64(i)
			if _, err := db.GetEngine(ctx).ID(issue.ID).Cols("project_board_id", "sorting").Update(issue); err != nil {
				return err
			}
		}
		return nil
	})
}

// DeleteAllProjectIssueByIssueIDsAndProjectIDs delete all project's issues by issue's and project's ids
func DeleteAllProjectIssueByIssueIDsAndProjectIDs(ctx context.Context, issueIDs, projectIDs []int64) error {
	_, err := db.GetEngine(ctx).In("project_id", projectIDs).In("issue_id", issueIDs).Delete(&ProjectIssue{})
	return err
}

// MoveIssueToColumn moves a single issue to a specific column within a project.
func MoveIssueToColumn(ctx context.Context, issueID, projectID, columnID int64) error {
	nextSorting, err := GetColumnIssueNextSorting(ctx, projectID, columnID)
	if err != nil {
		return err
	}
	_, err = db.GetEngine(ctx).
		Where("issue_id=? AND project_id=?", issueID, projectID).
		Cols("project_board_id", "sorting").
		Update(&ProjectIssue{
			ProjectColumnID: columnID,
			Sorting:         nextSorting,
		})
	return err
}
