package repo

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	ent "github.com/tidbcloud/devops/entity/kernelimage"
	mod "github.com/tidbcloud/devops/models/kernelimage"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/repoopt"
	"github.com/tidbcloud/devops/pkg/service/kernelimage/repo/cvt"
	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

var ErrImmutableArtifactConflict = errors.New("kernel image artifact immutable fields conflict")

type ListFilter struct {
	LocalDevOpsEnv string
	Repository     string
	Version        string
	Statuses       []ent.ReleaseStatus
	Masked         *bool
	Page           int
	PerPage        int
}

type DeliveryResultUpdate struct {
	ArtifactUID  string
	TargetImages []string
	Availability []*ent.PlatformAvailability
}

type Repo interface {
	CreateRelease(ctx context.Context, release *ent.Release) (*ent.Release, error)
	GetReleaseByArtifactUID(ctx context.Context, artifactUID string) (*ent.Release, error)
	GetReleaseByArtifactUIDForUpdate(ctx context.Context, artifactUID string) (*ent.Release, error)
	ListReleasesByDeliveryBatchUID(ctx context.Context, batchUID string) ([]*ent.Release, error)
	ListReleasesByDeliveryBatchUIDForUpdate(ctx context.Context, batchUID string) ([]*ent.Release, error)
	SetReleaseSourceDigest(ctx context.Context, artifactUID string, instanceID uint64, digest string) (*ent.Release, error)
	SetReleaseDeliveryOperationInstance(ctx context.Context, artifactUID string, instanceID uint64) (*ent.Release, error)
	SetBatchDeliveryOperationInstance(ctx context.Context, batchUID string, instanceID uint64) ([]*ent.Release, error)
	MarkReleaseDeliveryOperationCreateFailed(ctx context.Context, artifactUID, errorCode, errorMessage string) (*ent.Release, error)
	MarkBatchDeliveryOperationCreateFailed(ctx context.Context, batchUID, errorCode, errorMessage string) ([]*ent.Release, error)
	MarkReleaseSyncing(ctx context.Context, artifactUID string, instanceID uint64) (*ent.Release, error)
	MarkBatchSyncing(ctx context.Context, batchUID string, instanceID uint64) ([]*ent.Release, error)
	MarkReleaseSyncSucceeded(ctx context.Context, artifactUID string, instanceID uint64, availableAt time.Time, targetImages []string, availability []*ent.PlatformAvailability) (*ent.Release, error)
	MarkBatchFIDDelivered(ctx context.Context, batchUID string, instanceID uint64, deliveredAt time.Time, updates []DeliveryResultUpdate) ([]*ent.Release, error)
	MarkBatchSyncSucceeded(ctx context.Context, batchUID string, instanceID uint64, availableAt time.Time, updates []DeliveryResultUpdate) ([]*ent.Release, error)
	MarkBatchAvailable(ctx context.Context, batchUID string, instanceID uint64, availableAt time.Time) ([]*ent.Release, error)
	MarkReleaseSyncFailed(ctx context.Context, artifactUID string, instanceID uint64, errorCode, errorMessage string) (*ent.Release, error)
	MarkBatchSyncFailed(ctx context.Context, batchUID string, instanceID uint64, errorCode, errorMessage string) ([]*ent.Release, error)
	MarkAvailabilityChecking(ctx context.Context, artifactUID, checkUID string, startedAt time.Time, dueBefore *time.Time) (*ent.Release, error)
	UpdateAvailability(ctx context.Context, artifactUID, checkUID string, status ent.AvailabilityStatus, availability []*ent.PlatformAvailability, checkedAt time.Time, lastError string) (*ent.Release, error)
	ListReleases(ctx context.Context, filter ListFilter) ([]*ent.Release, int64, error)
	ListReleasesForSelection(ctx context.Context, localEnv string, repositories []string) ([]*ent.Release, error)
	ListReleasesForAvailability(ctx context.Context, localEnv string, dueBefore time.Time) ([]*ent.Release, error)
}

type repo struct {
	dbp   db.TransProvider
	cvter *cvt.Converter
}

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

func (r *repo) CreateRelease(ctx context.Context, release *ent.Release) (*ent.Release, error) {
	var result *ent.Release
	err := r.dbp.Transaction(ctx, func(txCtx context.Context) error {
		existing, err := r.getReleaseByArtifactUID(
			r.dbp.WriteDB(txCtx).Clauses(clause.Locking{Strength: "UPDATE"}),
			release.ArtifactUID,
		)
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}
		if existing != nil {
			if err := verifyIdempotentRelease(existing, release); err != nil {
				return err
			}
			result, err = r.enrichOptionalReleaseMetadata(txCtx, existing, release)
			return err
		}

		model := r.cvter.EntityToModel().Release(release)
		if err := r.dbp.WriteDB(txCtx).Create(model).Error; err != nil {
			return fmt.Errorf("create kernel image release: %w", err)
		}
		result, err = r.getReleaseByArtifactUID(r.dbp.WriteDB(txCtx), release.ArtifactUID)
		return err
	})
	if err != nil {
		// A concurrent callback may win the unique artifact_uid insert after this
		// transaction observes no row. Treat the committed identical artifact as
		// an idempotent success; unrelated unique-key failures still return err.
		existing, loadErr := r.GetReleaseByArtifactUID(ctx, release.ArtifactUID)
		if loadErr == nil {
			if verifyErr := verifyIdempotentRelease(existing, release); verifyErr == nil {
				return existing, nil
			}
		}
	}
	return result, err
}

func (r *repo) enrichOptionalReleaseMetadata(
	ctx context.Context,
	existing, incoming *ent.Release,
) (*ent.Release, error) {
	updates := map[string]any{}
	for column, values := range map[string][2]string{
		"branch":            {existing.Branch, incoming.Branch},
		"git_tag":           {existing.GitTag, incoming.GitTag},
		"source_digest":     {existing.SourceDigest, incoming.SourceDigest},
		"source_release_id": {existing.SourceReleaseID, incoming.SourceReleaseID},
		"source_change_id":  {existing.SourceChangeID, incoming.SourceChangeID},
	} {
		if values[0] == "" && values[1] != "" {
			updates[column] = values[1]
		}
	}
	if len(updates) == 0 {
		return existing, nil
	}
	for column, value := range updates {
		if err := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
			Where("artifact_uid = ?", existing.ArtifactUID).
			Where(clause.Eq{Column: column, Value: ""}).
			Update(column, value).Error; err != nil {
			return nil, fmt.Errorf("enrich kernel image release metadata: %w", err)
		}
	}
	enriched, err := r.getReleaseByArtifactUID(r.dbp.WriteDB(ctx), existing.ArtifactUID)
	if err != nil {
		return nil, err
	}
	if err := verifyIdempotentRelease(enriched, incoming); err != nil {
		return nil, err
	}
	return enriched, nil
}

func (r *repo) GetReleaseByArtifactUID(ctx context.Context, artifactUID string) (*ent.Release, error) {
	return r.getReleaseByArtifactUID(r.dbp.ReadDB(ctx), artifactUID)
}

func (r *repo) GetReleaseByArtifactUIDForUpdate(ctx context.Context, artifactUID string) (*ent.Release, error) {
	return r.getReleaseByArtifactUID(
		r.dbp.WriteDB(ctx).Clauses(clause.Locking{Strength: "UPDATE"}),
		artifactUID,
	)
}

func (r *repo) ListReleasesByDeliveryBatchUID(ctx context.Context, batchUID string) ([]*ent.Release, error) {
	return r.getReleasesByDeliveryBatchUID(r.dbp.ReadDB(ctx), batchUID)
}

func (r *repo) ListReleasesByDeliveryBatchUIDForUpdate(ctx context.Context, batchUID string) ([]*ent.Release, error) {
	return r.getReleasesByDeliveryBatchUID(
		r.dbp.WriteDB(ctx).Clauses(clause.Locking{Strength: "UPDATE"}),
		batchUID,
	)
}

func (r *repo) SetReleaseSourceDigest(
	ctx context.Context,
	artifactUID string,
	instanceID uint64,
	digest string,
) (*ent.Release, error) {
	var result *ent.Release
	err := r.dbp.Transaction(ctx, func(txCtx context.Context) error {
		release, err := r.GetReleaseByArtifactUIDForUpdate(txCtx, artifactUID)
		if err != nil {
			return err
		}
		if release.DeliveryOperationInstanceID == nil || *release.DeliveryOperationInstanceID != instanceID {
			return fmt.Errorf(
				"kernel image release %s is not linked to delivery operation %d",
				artifactUID,
				instanceID,
			)
		}
		if release.SourceDigest != "" {
			if release.SourceDigest != digest {
				return fmt.Errorf("%w: source digest for artifact_uid %s", ErrImmutableArtifactConflict, artifactUID)
			}
			result = release
			return nil
		}
		if err := r.dbp.WriteDB(txCtx).Model(&mod.Release{}).
			Where("artifact_uid = ?", artifactUID).
			Update("source_digest", digest).Error; err != nil {
			return fmt.Errorf("set kernel image release source digest: %w", err)
		}
		result, err = r.getReleaseByArtifactUID(r.dbp.WriteDB(txCtx), artifactUID)
		return err
	})
	return result, err
}

func (r *repo) SetReleaseDeliveryOperationInstance(ctx context.Context, artifactUID string, instanceID uint64) (*ent.Release, error) {
	result := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where("artifact_uid = ? AND delivery_operation_instance_id IS NULL", artifactUID).
		Updates(map[string]any{
			"delivery_operation_instance_id": instanceID,
			"status":                         ent.ReleaseStatusDeliveryApproving,
			"last_error_code":                "",
			"last_error_message":             "",
		})
	if result.Error != nil {
		return nil, fmt.Errorf("set kernel image release delivery operation instance: %w", result.Error)
	}
	if result.RowsAffected != 1 {
		return nil, fmt.Errorf("set kernel image release delivery operation instance: expected one row, updated %d", result.RowsAffected)
	}
	return r.getReleaseByArtifactUID(r.dbp.WriteDB(ctx), artifactUID)
}

func (r *repo) SetBatchDeliveryOperationInstance(
	ctx context.Context,
	batchUID string,
	instanceID uint64,
) ([]*ent.Release, error) {
	releases, err := r.ListReleasesByDeliveryBatchUIDForUpdate(ctx, batchUID)
	if err != nil {
		return nil, err
	}
	result := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where("delivery_batch_uid = ? AND delivery_operation_instance_id IS NULL", batchUID).
		Updates(map[string]any{
			"delivery_operation_instance_id": instanceID,
			"status":                         ent.ReleaseStatusDeliveryApproving,
			"last_error_code":                "",
			"last_error_message":             "",
		})
	if result.Error != nil {
		return nil, fmt.Errorf("set kernel image batch delivery operation instance: %w", result.Error)
	}
	if result.RowsAffected != int64(len(releases)) {
		return nil, fmt.Errorf(
			"set kernel image batch delivery operation instance: expected %d rows, updated %d",
			len(releases),
			result.RowsAffected,
		)
	}
	return r.getReleasesByDeliveryBatchUID(r.dbp.WriteDB(ctx), batchUID)
}

func (r *repo) MarkReleaseDeliveryOperationCreateFailed(
	ctx context.Context,
	artifactUID, errorCode, errorMessage string,
) (*ent.Release, error) {
	result := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where("artifact_uid = ? AND delivery_operation_instance_id IS NULL", artifactUID).
		Updates(map[string]any{
			"status":             ent.ReleaseStatusDeliveryOperationCreateFailed,
			"last_error_code":    errorCode,
			"last_error_message": errorMessage,
		})
	if result.Error != nil {
		return nil, fmt.Errorf("mark kernel image delivery operation creation failed: %w", result.Error)
	}
	return r.getReleaseByArtifactUID(r.dbp.WriteDB(ctx), artifactUID)
}

func (r *repo) MarkBatchDeliveryOperationCreateFailed(
	ctx context.Context,
	batchUID, errorCode, errorMessage string,
) ([]*ent.Release, error) {
	result := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where("delivery_batch_uid = ? AND delivery_operation_instance_id IS NULL", batchUID).
		Updates(map[string]any{
			"status":             ent.ReleaseStatusDeliveryOperationCreateFailed,
			"last_error_code":    errorCode,
			"last_error_message": errorMessage,
		})
	if result.Error != nil {
		return nil, fmt.Errorf("mark kernel image batch delivery operation creation failed: %w", result.Error)
	}
	return r.getReleasesByDeliveryBatchUID(r.dbp.WriteDB(ctx), batchUID)
}

func (r *repo) MarkReleaseSyncing(ctx context.Context, artifactUID string, instanceID uint64) (*ent.Release, error) {
	return r.updateDeliveryStatus(ctx, artifactUID, instanceID, map[string]any{
		"status":             ent.ReleaseStatusSyncing,
		"last_error_code":    "",
		"last_error_message": "",
	})
}

func (r *repo) MarkBatchSyncing(ctx context.Context, batchUID string, instanceID uint64) ([]*ent.Release, error) {
	return r.updateBatchDeliveryStatus(ctx, batchUID, instanceID, map[string]any{
		"status":             ent.ReleaseStatusSyncing,
		"last_error_code":    "",
		"last_error_message": "",
	})
}

func (r *repo) MarkReleaseSyncSucceeded(
	ctx context.Context,
	artifactUID string,
	instanceID uint64,
	availableAt time.Time,
	targetImages []string,
	availability []*ent.PlatformAvailability,
) (*ent.Release, error) {
	targetImagesJSON, err := json.Marshal(targetImages)
	if err != nil {
		return nil, fmt.Errorf("marshal kernel image delivery targets: %w", err)
	}
	availabilityJSON, err := json.Marshal(availability)
	if err != nil {
		return nil, fmt.Errorf("marshal kernel image availability: %w", err)
	}
	availabilityStatus := ent.AvailabilityStatusUnknown
	var availabilityCheckedAt *time.Time
	if len(availability) > 0 {
		availabilityStatus = ent.AvailabilityStatusAvailable
		availabilityCheckedAt = &availableAt
	}
	return r.updateDeliveryStatus(ctx, artifactUID, instanceID, map[string]any{
		"status":                  ent.ReleaseStatusAvailable,
		"available_at":            availableAt,
		"delivery_target_images":  targetImagesJSON,
		"availability_status":     availabilityStatus,
		"availability_snapshot":   availabilityJSON,
		"availability_checked_at": availabilityCheckedAt,
		"availability_last_error": "",
		"last_error_code":         "",
		"last_error_message":      "",
	})
}

func (r *repo) MarkBatchSyncSucceeded(
	ctx context.Context,
	batchUID string,
	instanceID uint64,
	availableAt time.Time,
	updates []DeliveryResultUpdate,
) ([]*ent.Release, error) {
	return r.saveBatchDeliveryResult(ctx, batchUID, instanceID, availableAt, updates, true)
}

func (r *repo) MarkBatchFIDDelivered(
	ctx context.Context,
	batchUID string,
	instanceID uint64,
	deliveredAt time.Time,
	updates []DeliveryResultUpdate,
) ([]*ent.Release, error) {
	return r.saveBatchDeliveryResult(ctx, batchUID, instanceID, deliveredAt, updates, false)
}

func (r *repo) saveBatchDeliveryResult(
	ctx context.Context,
	batchUID string,
	instanceID uint64,
	deliveredAt time.Time,
	updates []DeliveryResultUpdate,
	markAvailable bool,
) ([]*ent.Release, error) {
	var result []*ent.Release
	err := r.dbp.Transaction(ctx, func(txCtx context.Context) error {
		releases, err := r.ListReleasesByDeliveryBatchUIDForUpdate(txCtx, batchUID)
		if err != nil {
			return err
		}
		if len(releases) != len(updates) {
			return fmt.Errorf(
				"mark kernel image batch sync succeeded: expected %d updates, got %d",
				len(releases),
				len(updates),
			)
		}
		updatesByArtifact := make(map[string]DeliveryResultUpdate, len(updates))
		for _, update := range updates {
			if _, exists := updatesByArtifact[update.ArtifactUID]; exists {
				return fmt.Errorf("mark kernel image batch sync succeeded: duplicate artifact %s", update.ArtifactUID)
			}
			updatesByArtifact[update.ArtifactUID] = update
		}
		for _, release := range releases {
			if release.DeliveryOperationInstanceID == nil || *release.DeliveryOperationInstanceID != instanceID {
				return fmt.Errorf(
					"kernel image release %s is not linked to delivery operation %d",
					release.ArtifactUID,
					instanceID,
				)
			}
			update, ok := updatesByArtifact[release.ArtifactUID]
			if !ok {
				return fmt.Errorf("mark kernel image batch sync succeeded: artifact %s is missing", release.ArtifactUID)
			}
			targetImagesJSON, err := json.Marshal(update.TargetImages)
			if err != nil {
				return fmt.Errorf("marshal kernel image delivery targets: %w", err)
			}
			availabilityJSON, err := json.Marshal(update.Availability)
			if err != nil {
				return fmt.Errorf("marshal kernel image availability: %w", err)
			}
			availabilityStatus := ent.AvailabilityStatusUnknown
			var availabilityCheckedAt *time.Time
			if len(update.Availability) > 0 {
				availabilityStatus = ent.AvailabilityStatusAvailable
				availabilityCheckedAt = &deliveredAt
			}
			status := ent.ReleaseStatusSyncing
			var availableAt *time.Time
			if markAvailable {
				status = ent.ReleaseStatusAvailable
				availableAt = &deliveredAt
			}
			updateResult := r.dbp.WriteDB(txCtx).Model(&mod.Release{}).
				Where("artifact_uid = ? AND delivery_batch_uid = ? AND delivery_operation_instance_id = ?", release.ArtifactUID, batchUID, instanceID).
				Updates(map[string]any{
					"status":                  status,
					"available_at":            availableAt,
					"delivery_target_images":  targetImagesJSON,
					"availability_status":     availabilityStatus,
					"availability_snapshot":   availabilityJSON,
					"availability_checked_at": availabilityCheckedAt,
					"availability_last_error": "",
					"last_error_code":         "",
					"last_error_message":      "",
				})
			if updateResult.Error != nil {
				return fmt.Errorf("mark kernel image release sync succeeded: %w", updateResult.Error)
			}
			if updateResult.RowsAffected != 1 {
				return fmt.Errorf("mark kernel image release sync succeeded: expected one row, updated %d", updateResult.RowsAffected)
			}
		}
		result, err = r.getReleasesByDeliveryBatchUID(r.dbp.WriteDB(txCtx), batchUID)
		return err
	})
	return result, err
}

func (r *repo) MarkBatchAvailable(
	ctx context.Context,
	batchUID string,
	instanceID uint64,
	availableAt time.Time,
) ([]*ent.Release, error) {
	return r.updateBatchDeliveryStatus(ctx, batchUID, instanceID, map[string]any{
		"status":             ent.ReleaseStatusAvailable,
		"available_at":       availableAt,
		"last_error_code":    "",
		"last_error_message": "",
	})
}

func (r *repo) MarkReleaseSyncFailed(
	ctx context.Context,
	artifactUID string,
	instanceID uint64,
	errorCode, errorMessage string,
) (*ent.Release, error) {
	return r.updateDeliveryStatus(ctx, artifactUID, instanceID, map[string]any{
		"status":             ent.ReleaseStatusSyncFailed,
		"last_error_code":    errorCode,
		"last_error_message": errorMessage,
	})
}

func (r *repo) MarkBatchSyncFailed(
	ctx context.Context,
	batchUID string,
	instanceID uint64,
	errorCode, errorMessage string,
) ([]*ent.Release, error) {
	return r.updateBatchDeliveryStatus(ctx, batchUID, instanceID, map[string]any{
		"status":             ent.ReleaseStatusSyncFailed,
		"last_error_code":    errorCode,
		"last_error_message": errorMessage,
	})
}

func (r *repo) updateBatchDeliveryStatus(
	ctx context.Context,
	batchUID string,
	instanceID uint64,
	updates map[string]any,
) ([]*ent.Release, error) {
	releases, err := r.ListReleasesByDeliveryBatchUID(ctx, batchUID)
	if err != nil {
		return nil, err
	}
	result := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where("delivery_batch_uid = ? AND delivery_operation_instance_id = ?", batchUID, instanceID).
		Updates(updates)
	if result.Error != nil {
		return nil, fmt.Errorf("update kernel image batch delivery status: %w", result.Error)
	}
	if result.RowsAffected != int64(len(releases)) {
		return nil, fmt.Errorf(
			"update kernel image batch delivery status: expected %d rows, updated %d",
			len(releases),
			result.RowsAffected,
		)
	}
	return r.getReleasesByDeliveryBatchUID(r.dbp.WriteDB(ctx), batchUID)
}

func (r *repo) updateDeliveryStatus(
	ctx context.Context,
	artifactUID string,
	instanceID uint64,
	updates map[string]any,
) (*ent.Release, error) {
	result := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where("artifact_uid = ? AND delivery_operation_instance_id = ?", artifactUID, instanceID).
		Updates(updates)
	if result.Error != nil {
		return nil, fmt.Errorf("update kernel image delivery status: %w", result.Error)
	}
	release, err := r.getReleaseByArtifactUID(r.dbp.WriteDB(ctx), artifactUID)
	if err != nil {
		return nil, err
	}
	if release.DeliveryOperationInstanceID == nil || *release.DeliveryOperationInstanceID != instanceID {
		return nil, fmt.Errorf(
			"kernel image release %s is not linked to delivery operation %d",
			artifactUID,
			instanceID,
		)
	}
	return release, nil
}

func (r *repo) MarkAvailabilityChecking(
	ctx context.Context,
	artifactUID, checkUID string,
	startedAt time.Time,
	dueBefore *time.Time,
) (*ent.Release, error) {
	query := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where(
			"artifact_uid = ? AND (availability_status <> ? OR availability_check_started_at IS NULL OR availability_check_started_at < ?)",
			artifactUID,
			ent.AvailabilityStatusChecking,
			startedAt.Add(-30*time.Minute),
		)
	if dueBefore != nil {
		query = query.Where("availability_checked_at IS NULL OR availability_checked_at < ?", *dueBefore)
	}
	result := query.
		Updates(map[string]any{
			"availability_status":           ent.AvailabilityStatusChecking,
			"availability_check_uid":        checkUID,
			"availability_check_started_at": startedAt,
			"availability_last_error":       "",
		})
	if result.Error != nil {
		return nil, fmt.Errorf("mark kernel image availability checking: %w", result.Error)
	}
	if result.RowsAffected > 1 {
		return nil, fmt.Errorf("mark kernel image availability checking: expected at most one row, updated %d", result.RowsAffected)
	}
	return r.getReleaseByArtifactUID(r.dbp.WriteDB(ctx), artifactUID)
}

func (r *repo) UpdateAvailability(
	ctx context.Context,
	artifactUID, checkUID string,
	status ent.AvailabilityStatus,
	availability []*ent.PlatformAvailability,
	checkedAt time.Time,
	lastError string,
) (*ent.Release, error) {
	availabilityJSON, err := json.Marshal(availability)
	if err != nil {
		return nil, fmt.Errorf("marshal kernel image availability: %w", err)
	}
	result := r.dbp.WriteDB(ctx).Model(&mod.Release{}).
		Where("artifact_uid = ? AND availability_check_uid = ?", artifactUID, checkUID).
		Updates(map[string]any{
			"availability_status":           status,
			"availability_snapshot":         availabilityJSON,
			"availability_checked_at":       checkedAt,
			"availability_check_started_at": nil,
			"availability_last_error":       lastError,
		})
	if result.Error != nil {
		return nil, fmt.Errorf("update kernel image availability: %w", result.Error)
	}
	if result.RowsAffected != 1 {
		return nil, fmt.Errorf("update kernel image availability: stale check %s for artifact %s", checkUID, artifactUID)
	}
	return r.getReleaseByArtifactUID(r.dbp.WriteDB(ctx), artifactUID)
}

func (r *repo) getReleaseByArtifactUID(query *gorm.DB, artifactUID string) (*ent.Release, error) {
	var releaseModel mod.Release
	if err := query.Where("artifact_uid = ?", artifactUID).First(&releaseModel).Error; err != nil {
		return nil, err
	}
	return r.cvter.ModelToEntity().Release(&releaseModel), nil
}

func (r *repo) getReleasesByDeliveryBatchUID(query *gorm.DB, batchUID string) ([]*ent.Release, error) {
	var releaseModels []*mod.Release
	if err := query.Where("delivery_batch_uid = ?", batchUID).Order("id ASC").Find(&releaseModels).Error; err != nil {
		return nil, err
	}
	if len(releaseModels) == 0 {
		return nil, gorm.ErrRecordNotFound
	}
	return container.TransformSlice(releaseModels, r.cvter.ModelToEntity().Release), nil
}

func (r *repo) ListReleases(ctx context.Context, filter ListFilter) ([]*ent.Release, int64, error) {
	query := applyListFilter(r.dbp.ReadDB(ctx).Model(&mod.Release{}), filter)
	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	page, perPage := filter.Page, filter.PerPage
	if page <= 0 {
		page = 1
	}
	if perPage <= 0 {
		perPage = 20
	}
	query = repoopt.Aggregate(
		&repoopt.Pager{Page: page, PerPage: perPage},
		&repoopt.Order{Column: "created_at", Desc: true},
	).Apply(query)
	result, err := r.queryReleases(query)
	return result, total, err
}

func (r *repo) ListReleasesForSelection(ctx context.Context, localEnv string, repositories []string) ([]*ent.Release, error) {
	if len(repositories) == 0 {
		return []*ent.Release{}, nil
	}
	query := applyListFilter(r.dbp.ReadDB(ctx).Model(&mod.Release{}), ListFilter{LocalDevOpsEnv: localEnv}).
		Where("source_repository IN ?", repositories)
	return r.queryReleases(query.Order("created_at DESC"))
}

func (r *repo) ListReleasesForAvailability(ctx context.Context, localEnv string, dueBefore time.Time) ([]*ent.Release, error) {
	query := r.dbp.ReadDB(ctx).Model(&mod.Release{}).
		Where("local_dev_ops_env = ? AND status = ? AND masked = ?", localEnv, ent.ReleaseStatusAvailable, false).
		Where("availability_checked_at IS NULL OR availability_checked_at < ?", dueBefore).
		Order("availability_checked_at ASC, created_at ASC")
	return r.queryReleases(query)
}

func (r *repo) queryReleases(query *gorm.DB) ([]*ent.Release, error) {
	var releaseModels []*mod.Release
	if err := query.Find(&releaseModels).Error; err != nil {
		return nil, err
	}
	return container.TransformSlice(releaseModels, r.cvter.ModelToEntity().Release), nil
}

func applyListFilter(query *gorm.DB, filter ListFilter) *gorm.DB {
	if filter.LocalDevOpsEnv != "" {
		query = query.Where("local_dev_ops_env = ?", filter.LocalDevOpsEnv)
	}
	if filter.Repository != "" {
		query = query.Where("source_repository = ?", filter.Repository)
	}
	if filter.Version != "" {
		query = query.Where("version = ?", filter.Version)
	}
	if len(filter.Statuses) > 0 {
		query = query.Where("status IN ?", filter.Statuses)
	}
	if filter.Masked != nil {
		query = query.Where("masked = ?", *filter.Masked)
	}
	return query
}

func verifyIdempotentRelease(existing, incoming *ent.Release) error {
	if existing.LocalDevOpsEnv != incoming.LocalDevOpsEnv ||
		existing.Version != incoming.Version ||
		existing.Repo != incoming.Repo ||
		existing.CommitSHA != incoming.CommitSHA ||
		existing.SourceRepository != incoming.SourceRepository ||
		existing.SourceTag != incoming.SourceTag ||
		existing.SourceApplicant != incoming.SourceApplicant ||
		existing.FormalDeliveryUID != incoming.FormalDeliveryUID ||
		existing.DeliveryBatchUID != incoming.DeliveryBatchUID ||
		optionalMetadataConflicts(existing.Branch, incoming.Branch) ||
		optionalMetadataConflicts(existing.GitTag, incoming.GitTag) ||
		optionalMetadataConflicts(existing.SourceDigest, incoming.SourceDigest) ||
		optionalMetadataConflicts(existing.SourceReleaseID, incoming.SourceReleaseID) ||
		optionalMetadataConflicts(existing.SourceChangeID, incoming.SourceChangeID) {
		return fmt.Errorf("%w: artifact_uid %s", ErrImmutableArtifactConflict, incoming.ArtifactUID)
	}
	return nil
}

func optionalMetadataConflicts(existing, incoming string) bool {
	return existing != "" && incoming != "" && existing != incoming
}
