//go:generate ${GOBIN}/mockgen -destination repo_mock.go -package=repo ${GO_MODULE}/pkg/service/cloudconfig/repo CloudConfigRepo
package repo

import (
	"context"
	"fmt"

	ccent "github.com/tidbcloud/devops/entity/cloudconfig"
	"github.com/tidbcloud/devops/entity/validation"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/cloudconfig/repo/cvt"

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

type (
	PatchConfigurationChange func(current *ccent.ConfigurationChange) error
)

type CloudConfigRepo interface {
	// ConfigurationChange
	ListConfigurationChanges(ctx context.Context, opts ...repoopt.Option) ([]*ccent.ConfigurationChange, error)
	GetConfigurationChange(ctx context.Context, id uint64) (*ccent.ConfigurationChange, error)
	CreateConfigurationChange(ctx context.Context, change *ccent.ConfigurationChange) (*ccent.ConfigurationChange, error)
	PatchConfigurationChange(ctx context.Context, current *ccent.ConfigurationChange, patch ...PatchConfigurationChange) error
	CountConfigurationChange(ctx context.Context, opts ...repoopt.Option) (int64, error)

	// DefaultConfigurationChange
	GetDefaultConfigurationChange(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChange, error)
	ListDefaultConfigurationChanges(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChange, error)
	CreateDefaultConfigurationChange(ctx context.Context, newChange *ccent.DefaultConfigurationChange) (*ccent.DefaultConfigurationChange, error)
	UpdateDefaultConfigurationChange(ctx context.Context, change *ccent.DefaultConfigurationChange) error
	CountDefaultConfigurationChange(ctx context.Context, opts ...repoopt.Option) (int64, error)

	GetDefaultConfigurationChangeRelease(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChangeRelease, error)
	ListDefaultConfigurationChangeRelease(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error)
	CreateDefaultConfigurationChangeRelease(ctx context.Context, release *ccent.DefaultConfigurationChangeRelease) (*ccent.DefaultConfigurationChangeRelease, error)
	UpdateDefaultConfigurationChangeRelease(ctx context.Context, release *ccent.DefaultConfigurationChangeRelease) (*ccent.DefaultConfigurationChangeRelease, error)
	ListLatestDefaultConfigurationChangeReleasePerEnvironment(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error)

	ListDefaultConfigurationChangeItems(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItem, error)
	BatchUpsertDefaultConfigurationChangeItems(ctx context.Context, changeId uint64, items []*ccent.DefaultConfigurationChangeItem) error
	CountDefaultConfigurationChangeItems(ctx context.Context, opts ...repoopt.Option) (int64, error)
	StatDefaultConfigurationChangeItems(ctx context.Context, result any, opts ...repoopt.Option) error

	ListDefaultConfigurationChangeItemReleases(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error)
	BatchCreateDefaultConfigurationChangeItemReleases(ctx context.Context, changeId uint64, releaseNr uint64, releases []*ccent.DefaultConfigurationChangeItemRelease) error
	ListLastestDefaultConfigurationChangeItemReleasesPerItem(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error)
}

func NewCloudConfigRepo(dbp db.Provider) CloudConfigRepo {
	driver := &driver{
		cvter:      cvt.NewConverter(),
		dbProvider: dbp,
	}

	return &repo{
		ccDriver: (*ccDriver)(driver),
		tcDriver: (*tcDriver)(driver),
	}
}

type driver struct {
	cvter      *cvt.Converter
	dbProvider db.Provider
}

type repo struct {
	ccDriver ConfigurationChangeDriver
	tcDriver DefaultConfigurationChangeDriver
}

func (r *repo) ListConfigurationChanges(ctx context.Context, opts ...repoopt.Option) ([]*ccent.ConfigurationChange, error) {
	return r.ccDriver.Query(ctx, opts...)
}

func (r *repo) GetConfigurationChange(ctx context.Context, id uint64) (*ccent.ConfigurationChange, error) {
	return r.ccDriver.Load(ctx, id)
}

func (r *repo) CreateConfigurationChange(ctx context.Context, modif *ccent.ConfigurationChange) (*ccent.ConfigurationChange, error) {
	err := validation.CloudConfig.ConfigurationChange(modif)
	if err != nil {
		return nil, fmt.Errorf("invalid configuration change: %w", err)
	}

	return r.ccDriver.Save(ctx, modif)
}

func (r *repo) PatchConfigurationChange(ctx context.Context, current *ccent.ConfigurationChange, patch ...PatchConfigurationChange) error {
	for _, p := range patch {
		err := p(current)
		if err != nil {
			return err
		}
	}

	err := validation.CloudConfig.ConfigurationChange(current)
	if err != nil {
		return fmt.Errorf("invalid configuration change: %w", err)
	}

	_, err = r.ccDriver.Save(ctx, current)
	return err
}

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

func (r *repo) ListDefaultConfigurationChanges(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChange, error) {
	return r.tcDriver.QueryDefaultConfigurationChanges(ctx, opts...)
}

func (r *repo) GetDefaultConfigurationChange(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChange, error) {
	return r.tcDriver.LoadDefaultConfigurationChange(ctx, id)
}

func (r *repo) CountDefaultConfigurationChange(ctx context.Context, opts ...repoopt.Option) (int64, error) {
	return r.tcDriver.CountDefaultConfigurationChanges(ctx, opts...)
}

func (r *repo) CreateDefaultConfigurationChange(ctx context.Context, newChange *ccent.DefaultConfigurationChange) (*ccent.DefaultConfigurationChange, error) {
	err := validation.CloudConfig.DefaultConfigurationChange(newChange)
	if err != nil {
		return nil, fmt.Errorf("invalid template change: %w", err)
	}

	change, err := r.tcDriver.SaveDefaultConfigurationChange(ctx, newChange)
	if err != nil {
		return nil, fmt.Errorf("failed to save default config change: %w", err)
	}

	return change, nil
}

func (r *repo) UpdateDefaultConfigurationChange(ctx context.Context, change *ccent.DefaultConfigurationChange) error {
	err := validation.CloudConfig.DefaultConfigurationChange(change)
	if err != nil {
		return fmt.Errorf("invalid template change: %w", err)
	}

	_, err = r.tcDriver.SaveDefaultConfigurationChange(ctx, change)
	return err
}

func (r *repo) GetDefaultConfigurationChangeRelease(ctx context.Context, id uint64) (*ccent.DefaultConfigurationChangeRelease, error) {
	return r.tcDriver.LoadDefaultConfigurationChangeRelease(ctx, id)
}

func (r *repo) ListDefaultConfigurationChangeRelease(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error) {
	return r.tcDriver.QueryDefaultConfigurationChangeReleases(ctx, opts...)
}
func (r *repo) CreateDefaultConfigurationChangeRelease(ctx context.Context, release *ccent.DefaultConfigurationChangeRelease) (*ccent.DefaultConfigurationChangeRelease, error) {
	if release.ID != 0 {
		return nil, fmt.Errorf("id is not empty")
	}
	return r.tcDriver.SaveDefaultConfigurationChangeRelease(ctx, release)
}

func (r *repo) UpdateDefaultConfigurationChangeRelease(ctx context.Context, release *ccent.DefaultConfigurationChangeRelease) (*ccent.DefaultConfigurationChangeRelease, error) {
	if release.ID == 0 {
		return nil, fmt.Errorf("id is not empty")
	}
	return r.tcDriver.SaveDefaultConfigurationChangeRelease(ctx, release)
}

func (r *repo) ListLatestDefaultConfigurationChangeReleasePerEnvironment(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeRelease, error) {
	return r.tcDriver.QueryLatestDefaultConfigurationChangeReleasePerEnvironment(ctx, opts...)
}

func (r *repo) ListDefaultConfigurationChangeItemReleases(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error) {
	return r.tcDriver.QueryDefaultConfigurationChangeItemReleases(ctx, opts...)
}

func (r *repo) BatchCreateDefaultConfigurationChangeItemReleases(ctx context.Context, changeID uint64, releaseNr uint64, releases []*ccent.DefaultConfigurationChangeItemRelease) error {
	if changeID == 0 {
		return fmt.Errorf("change ID is empty")
	}

	for i := range releases {
		if releases[i].ID != 0 {
			return fmt.Errorf("id is not empty")
		}
		releases[i].ParentID = changeID
		releases[i].ReleaseNumber = releaseNr
	}

	err := r.tcDriver.BatchInsertDefaultConfigurationChangeItemReleases(ctx, releases)
	if err != nil {
		return fmt.Errorf("failed to save default config change item releases: %w", err)
	}

	return nil
}

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

func (r *repo) StatDefaultConfigurationChangeItems(ctx context.Context, result any, opts ...repoopt.Option) error {
	return r.tcDriver.StatDefaultConfigurationChangeItems(ctx, result, opts...)
}

func (r *repo) ListDefaultConfigurationChangeItems(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItem, error) {
	return r.tcDriver.QueryDefaultConfigurationChangeItems(ctx, opts...)
}

func (r *repo) BatchUpsertDefaultConfigurationChangeItems(ctx context.Context, changeID uint64, items []*ccent.DefaultConfigurationChangeItem) error {
	if changeID == 0 {
		return fmt.Errorf("change ID is empty")
	}

	for i := range items {
		if items[i].ParentID == 0 {
			items[i].ParentID = changeID
		}
		if items[i].ParentID != changeID {
			return fmt.Errorf("unmatched parent id for item %d", items[i].ID)
		}
	}

	err := r.tcDriver.BatchSaveDefaultConfigurationChangeItems(ctx, items)
	if err != nil {
		return fmt.Errorf("failed to save default config change items: %w", err)
	}

	return nil
}

func (r *repo) ListLastestDefaultConfigurationChangeItemReleasesPerItem(ctx context.Context, opts ...repoopt.Option) ([]*ccent.DefaultConfigurationChangeItemRelease, error) {
	return r.tcDriver.QueryLastestDefaultConfigurationChangeItemReleasesPerItem(ctx, opts...)
}
