package repo_imp

import (
	"context"
	"errors"
	"strings"
	"time"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/repo"
	"github.com/tidbcloud/serverless-service/internal/domain/vo"
	"gorm.io/gorm"
)

type branchRepoImp struct {
	dbProvider db.Provider
}

func NewBranchRepoImp(dbProvider db.Provider) repo.BranchRepo {
	return &branchRepoImp{dbProvider}
}

func (r *branchRepoImp) Create(ctx context.Context, record *entity.Branch) (err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.Create")
	defer func() { tracing.EndSpan(span, err) }()

	if record == nil {
		return nil
	}

	wdb := r.dbProvider.WriteDB(ctx)
	if err := wdb.Create(record).Error; err != nil {
		if strings.Contains(err.Error(), "Duplicate entry") {
			return bizerr.New(bizerr.ErrCodeRecordExist, "")
		}
		return bizerr.WrapGormError(
			err,
			"failed to Create Branch, name: %s, mainBranchID: %s",
			record.Name,
			record.MainBranchID,
		)
	}

	return nil
}

func (r *branchRepoImp) Update(
	ctx context.Context,
	record *entity.Branch,
	columns []string,
	where map[string]any,
) (err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.Update")
	defer func() { tracing.EndSpan(span, err) }()

	if record == nil || record.ID == "" {
		return nil
	}

	wdb := r.dbProvider.WriteDB(ctx)
	if len(columns) > 0 {
		wdb = wdb.Select(columns)
	}
	if len(where) > 0 {
		wdb = wdb.Where(where)
	}
	result := wdb.Updates(record)
	if result.Error != nil {
		if strings.Contains(result.Error.Error(), "Duplicate entry") {
			return bizerr.New(bizerr.ErrCodeRecordExist, "")
		}
		return bizerr.WrapGormError(result.Error, "failed to Update Branch: %s", record.ID)
	} else if result.RowsAffected == 0 {
		return bizerr.New(bizerr.ErrCodeRecordNotFound, "")
	}

	return nil
}

func (r *branchRepoImp) GetBranchByID(ctx context.Context, id string) (branch *entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.GetBranchByID")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	branch = &entity.Branch{}
	if err := rdb.Where("id = ? and status <> ?", id, "deleted").First(branch).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "")
		}
		return nil, bizerr.WrapGormError(err, "failed to get Branch, id: %s", id)
	}

	return branch, nil
}

func (r *branchRepoImp) CountBranchesByMainBranchID(
	ctx context.Context,
	mainBranchID string,
) (count int64, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.CountBranchesByMainBranchID")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.Model(&entity.Branch{}).Where("main_branch_id = ? and status <> ?", mainBranchID, "deleted").Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "failed to count Branches, mainBranchID: %s", mainBranchID)
	}

	return count, nil
}

func (r *branchRepoImp) CountBranchesByOrgID(ctx context.Context, orgID uint64) (count int64, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.CountBranchesByOrgID")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.Model(&entity.Branch{}).Where("tenant_id = ? and status <> ?", orgID, "deleted").Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "failed to count Branches, orgID: %d", orgID)
	}

	return count, nil
}

func (r *branchRepoImp) GetBranchByNameAndMainBranchID(
	ctx context.Context,
	branchName string,
	mainBranchID string,
) (branch *entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.GetBranchByNameAndMainBranchID")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	branch = &entity.Branch{}
	if err := rdb.Where("name = ? and main_branch_id = ? and status <> ?", branchName, mainBranchID, "deleted").First(branch).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "")
		}
		return nil, bizerr.WrapGormError(
			err,
			"failed to get Branch, name: %s, mainBranchID: %s",
			branchName,
			mainBranchID,
		)
	}

	return branch, nil
}

func (r *branchRepoImp) ListBranchesByMainBranchIDInPage(
	ctx context.Context,
	mainBranchID string,
	opt vo.ListBranchesOpt,
) (branches []*entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.ListBranchesByMainBranchIDInPage")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	ldb := rdb.Where("main_branch_id = ? and status <> ?", mainBranchID, "deleted")
	if opt.SortByTime {
		ldb = ldb.Order("created_at desc")
	}
	var offset int
	// pageToken is used by branch open api.
	if opt.PageTokenOffset != 0 {
		offset = opt.PageTokenOffset
	} else {
		offset = (int(opt.Page) - 1) * int(opt.PageSize)
	}
	if err := ldb.Limit(int(opt.PageSize)).Offset(offset).Find(&branches).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list Branches in page, mainBranchID: %s", mainBranchID)
	}

	return branches, nil
}

func (r *branchRepoImp) ListBranchesByMainBranchID(
	ctx context.Context,
	mainBranchID string,
	deleteAfterTime *time.Time,
) (branches []*entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.ListBranchesByMainBranchIDInPage")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if deleteAfterTime == nil {
		if err := rdb.Where("main_branch_id = ? and status <> ?", mainBranchID, "deleted").Find(&branches).Error; err != nil {
			return nil, bizerr.WrapGormError(err, "failed to list Branches, mainBranchID: %s", mainBranchID)
		}
	} else {
		if err := rdb.Where("main_branch_id = ? and ((deleted_time > ? and status = ?) or (status <> ?))", mainBranchID, deleteAfterTime, "deleted", "deleted").Find(&branches).Error; err != nil {
			return nil, bizerr.WrapGormError(err, "failed to list Branches, mainBranchID: %s, deleteAfterTime: %s", mainBranchID, deleteAfterTime.String())
		}
	}

	return branches, nil
}

func (r *branchRepoImp) CountBranchesByStatus(
	ctx context.Context,
) (branchStatusCount []*entity.BranchStatusCount, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.CountBranchesByStatus")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.Model(&entity.Branch{}).Select("status, count(*) as count").Group("status").Find(&branchStatusCount).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to count branches by status")
	}

	return branchStatusCount, nil
}

func (r *branchRepoImp) ListCreatingBranches(ctx context.Context) (branches []*entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.ListCreatingBranches")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.Where("status = ?", "creating").Find(&branches).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list creating Branches")
	}

	return branches, nil
}

func (r *branchRepoImp) ListBranchesByStatuses(
	ctx context.Context,
	statuses ...constant.BranchStatus,
) (branches []*entity.Branch, err bizerr.BizError) {
	return r.ListBranchesByFilter(ctx, repo.BranchFilter{Statuses: statuses})
}

func (r *branchRepoImp) ListBranchesByFilter(
	ctx context.Context,
	filter repo.BranchFilter,
) (branches []*entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.ListBranchesByFilter")
	defer func() { tracing.EndSpan(span, err) }()

	if len(filter.Statuses) == 0 {
		return branches, nil
	}

	rdb := r.dbProvider.ReadDB(ctx).Where("status in ?", filter.Statuses)
	if filter.CreatedAfter != nil {
		rdb = rdb.Where("created_at > ?", *filter.CreatedAfter)
	}
	if filter.CreatedAtOrBefore != nil {
		rdb = rdb.Where("created_at <= ?", *filter.CreatedAtOrBefore)
	}
	if err := rdb.Find(&branches).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list branches by filter")
	}

	return branches, nil
}

func (r *branchRepoImp) ListBranchesByMainBranchIDAndIDs(
	ctx context.Context,
	mainBranchID string,
	ids []string,
) (branches []*entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.ListBranchesByMainBranchIDAndIDs")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.Where("main_branch_id = ? and status <> ?", mainBranchID, "deleted").Where("id in ?", ids).Find(&branches).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list Branches")
	}

	return branches, nil
}

func (r *branchRepoImp) ListBranchesByParentID(
	ctx context.Context,
	mainBranchID string,
	parentID string,
) (branches []*entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.ListBranchesByParentID")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.Where("main_branch_id = ? and status <> ?", mainBranchID, "deleted").Where("parent_id = ?", parentID).Find(&branches).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list Branches")
	}

	return branches, nil
}

func (r *branchRepoImp) ListRunningBranches(ctx context.Context) (branches []*entity.Branch, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "branchRepo.ListRunningBranches")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	if err := rdb.Where("status = ?", "running").Find(&branches).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list running Branches")
	}

	return branches, nil
}
