package git

import (
	"context"
	"fmt"
	"time"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.opentelemetry.io/otel"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

var Tracer = otel.Tracer("git")

type GitHubAppTaskDAO interface {
	GetTasksByStatus(ctx context.Context, status TaskStatus) ([]*GithubAppTask, error)
	AddTasks(ctx context.Context, task *GithubAppTask) (*GithubAppTask, error)
	UpdateTasksStatus(ctx context.Context, status TaskStatus, taskIDs []uint64) error
	UpdateTaskMessage(ctx context.Context, task *GithubAppTask, message string) error
	UpdateBranchPreviousStatus(ctx context.Context, task *GithubAppTask, branchPreviousStatus string) error
	GetTasksToClean(ctx context.Context, timeBefore time.Time) ([]*GithubAppTask, error)
	DeleteTasks(ctx context.Context, tasks []*GithubAppTask) error
	GetAndUpdateTasksStatusWithLocking(ctx context.Context, status TaskStatus, targetStatus TaskStatus) ([]*GithubAppTask, error)
	CountTasksByStatus(ctx context.Context) ([]*TaskStatusCount, error)
}

type gitHubAppTaskDAOImpl struct {
	db *gorm.DB
}

func NewGitHubAppTaskDAO(db *gorm.DB) GitHubAppTaskDAO {
	return &gitHubAppTaskDAOImpl{
		db: db,
	}
}

func (d *gitHubAppTaskDAOImpl) GetTasksByStatus(ctx context.Context, status TaskStatus) (_ []*GithubAppTask, err error) {
	ctx, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.GetTasksByStatus")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tilog.Debug(ctx, fmt.Sprintf("Get GitHub App tasks by status, status:%s", string(status)))
	var tasks []*GithubAppTask
	if result := d.db.Where("status = ?", string(status)).Find(&tasks); result.Error != nil {
		return nil, errors.Trace(result.Error)
	}
	return tasks, nil
}

func (d *gitHubAppTaskDAOImpl) AddTasks(ctx context.Context, task *GithubAppTask) (_ *GithubAppTask, err error) {
	ctx, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.AddTasks")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tilog.Debug(ctx, fmt.Sprintf("add GitHub App task, task:%+v", *task))
	if result := d.db.Create(task); result.Error != nil {
		return task, errors.Trace(result.Error)
	}
	return task, nil
}

func (d *gitHubAppTaskDAOImpl) UpdateTasksStatus(ctx context.Context, status TaskStatus, taskIDs []uint64) (err error) {
	ctx, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.UpdateTasksStatus")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tilog.Debug(ctx, fmt.Sprintf("update tasks status, status:%s, ids:%+v", string(status), taskIDs))
	if len(taskIDs) == 0 {
		return nil
	}
	if result := d.db.Model(&GithubAppTask{}).Where("id in ?", taskIDs).Update("status", string(status)); result.Error != nil {
		return errors.Trace(result.Error)
	}
	return nil
}

func (d *gitHubAppTaskDAOImpl) UpdateTaskMessage(ctx context.Context, task *GithubAppTask, message string) (err error) {
	ctx, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.UpdateTaskMessage")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tilog.Debug(ctx, fmt.Sprintf("update tasks message, taskID:%d, message:%s", task.ID, message))
	if result := d.db.Model(task).Update("message", message); result.Error != nil {
		return errors.Trace(result.Error)
	}
	return nil
}

func (d *gitHubAppTaskDAOImpl) UpdateBranchPreviousStatus(ctx context.Context, task *GithubAppTask, branchPreviousStatus string) (err error) {
	ctx, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.UpdateBranchPreviousStatus")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tilog.Debug(ctx, fmt.Sprintf("update branch previous status, taskID:%d, status:%s", task.ID, branchPreviousStatus))
	if result := d.db.Model(task).Update("tidb_branch_previous_status", branchPreviousStatus); result.Error != nil {
		return errors.Trace(result.Error)
	}
	return nil
}

func (d *gitHubAppTaskDAOImpl) GetTasksToClean(ctx context.Context, timeBefore time.Time) (_ []*GithubAppTask, err error) {
	ctx, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.GetTasksToClean")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tilog.Debug(ctx, fmt.Sprintf("Get tasks to cleam time:%+v", timeBefore))
	var tasks []*GithubAppTask
	if result := d.db.Where("created_at < ?", timeBefore).Find(&tasks); result.Error != nil {
		return nil, errors.Trace(result.Error)
	}
	return tasks, nil
}

func (d *gitHubAppTaskDAOImpl) DeleteTasks(ctx context.Context, tasks []*GithubAppTask) (err error) {
	ctx, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.DeleteTasks")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tasksID := make([]uint64, 0, len(tasks))
	for _, tasks := range tasks {
		tasksID = append(tasksID, tasks.ID)
	}
	tilog.Debug(ctx, fmt.Sprintf("delete tasks: %+v", tasksID))
	if len(tasksID) == 0 {
		return nil
	}
	if result := d.db.Delete(&tasks); result.Error != nil {
		return errors.Trace(result.Error)
	}
	return nil
}

func (d *gitHubAppTaskDAOImpl) GetAndUpdateTasksStatusWithLocking(ctx context.Context, status TaskStatus, targetStatus TaskStatus) (_ []*GithubAppTask, err error) {
	_, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.GetAndUpdateTasksStatusWithLocking")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	tx := d.db.Begin()
	defer tx.Commit()

	var tasks []*GithubAppTask
	if result := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("status = ?", string(status)).Find(&tasks); result.Error != nil {
		return nil, errors.Trace(result.Error)
	}

	taskIDs := make([]uint64, 0, len(tasks))
	for _, task := range tasks {
		taskIDs = append(taskIDs, task.ID)
	}
	if len(taskIDs) == 0 {
		return nil, nil
	}
	if result := tx.Model(&GithubAppTask{}).Where("id in ?", taskIDs).Update("status", string(targetStatus)); result.Error != nil {
		return nil, errors.Trace(result.Error)
	}
	return tasks, nil
}

func (d *gitHubAppTaskDAOImpl) CountTasksByStatus(ctx context.Context) (taskStatusCount []*TaskStatusCount, err error) {
	_, span := Tracer.Start(ctx, "gitHubAppTaskDAOImpl.CountTasksByStatus")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	if result := d.db.Model(&GithubAppTask{}).Select("status, count(*) as count").Group("status").Find(&taskStatusCount); result.Error != nil {
		return nil, errors.Trace(result.Error)
	}

	return taskStatusCount, nil
}
