//go:generate ${GOBIN}/mockgen -destination repo_mock.go -package=ops_maintenance_window ${GO_MODULE}/pkg/service/clustermaintenance/repo/ops_maintenance_window Repo
package ops_maintenance_window

import (
	"context"
	"fmt"

	maintent "github.com/tidbcloud/devops/entity/maintenance"

	"github.com/tidbcloud/devops/api"
	"github.com/tidbcloud/devops/entity/validation"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/clustermaintenance/repo/cvt"

	"github.com/tidbcloud/pkgv2/db"
)

type Repo interface {
	List(ctx context.Context, opts ...repoopt.Option) ([]*maintent.ClusterOpsMaintenanceWindow, error)
	Get(ctx context.Context, clusterID string) (*maintent.ClusterOpsMaintenanceWindow, error)
	Create(ctx context.Context, spec *maintent.ClusterOpsMaintenanceWindow) (*maintent.ClusterOpsMaintenanceWindow, error)
	Update(ctx context.Context, spec *maintent.ClusterOpsMaintenanceWindow) (*maintent.ClusterOpsMaintenanceWindow, error)
	Delete(ctx context.Context, clusterID string) error
	Count(ctx context.Context, opts ...repoopt.Option) (int64, error)
	Options() opts
}

func NewRepo(dbp db.Provider) Repo {
	return &repo{
		driver: &driver{
			cvter:      cvt.NewConverter(),
			dbProvider: dbp,
		},
	}
}

type repo struct {
	driver Driver
}

func (r *repo) List(
	ctx context.Context,
	opts ...repoopt.Option,
) ([]*maintent.ClusterOpsMaintenanceWindow, error) {
	return r.driver.Query(ctx, opts...)
}

func (r *repo) Get(
	ctx context.Context,
	clusterID string,
) (*maintent.ClusterOpsMaintenanceWindow, error) {
	return r.driver.Load(ctx, clusterID)
}

func (r *repo) Create(
	ctx context.Context,
	spec *maintent.ClusterOpsMaintenanceWindow,
) (*maintent.ClusterOpsMaintenanceWindow, error) {
	if spec.ID != 0 {
		return nil, fmt.Errorf("id must be 0")
	}

	if err := validation.ClusterMaintenance.ClusterOpsMaintenanceWindows(spec.Windows); err != nil {
		return nil, err
	}

	_, err := r.driver.Load(ctx, spec.ClusterID)
	if err == nil {
		return nil, errs.APIErrorf(api.ErrCodeAlreadyExists, "cluster ops maintenance window of cluster %s already exists", spec.ClusterID)
	}
	if !errs.IsNotFound(err) {
		return nil, err
	}

	return r.driver.Save(ctx, spec)
}

func (r *repo) Update(
	ctx context.Context,
	spec *maintent.ClusterOpsMaintenanceWindow,
) (*maintent.ClusterOpsMaintenanceWindow, error) {
	if err := validation.ClusterMaintenance.ClusterOpsMaintenanceWindows(spec.Windows); err != nil {
		return nil, err
	}

	current, err := r.driver.Load(ctx, spec.ClusterID)
	if errs.IsNotFound(err) {
		return nil, errs.APIErrorf(api.ErrCodeNotFound, "cluster ops maintenance window of cluster %s not found", spec.ClusterID)
	}
	if err != nil {
		return nil, err
	}

	spec.ID = current.ID
	return r.driver.Save(ctx, spec)
}

func (r *repo) Delete(ctx context.Context, clusterID string) error {
	return r.driver.Delete(ctx, clusterID)
}

func (r *repo) Count(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	return r.driver.Count(ctx, opts...)
}

func (r *repo) Options() opts {
	return opts{}
}
