package mysql

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

	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"wucher/internal/domain/transient"
	"wucher/internal/queue"
)

const (
	idempotencyStateProcessing = "processing"
	idempotencyStateDone       = "done"
)

type QueueIdempotencyStore struct {
	db     *gorm.DB
	prefix string
	now    func() time.Time
}

func NewQueueIdempotencyStore(db *gorm.DB, prefix string) *QueueIdempotencyStore {
	return &QueueIdempotencyStore{
		db:     db,
		prefix: strings.TrimSpace(prefix),
		now:    func() time.Time { return time.Now().UTC() },
	}
}

func (s *QueueIdempotencyStore) Acquire(ctx context.Context, key string, processingTTL time.Duration) (queue.IdempotencyState, error) {
	if s == nil || s.db == nil {
		return queue.IdempotencyStateAcquired, errors.New("queue idempotency db is required")
	}
	storeKey := s.key(key)
	if storeKey == "" {
		return "", errors.New("queue idempotency key is required")
	}
	if processingTTL <= 0 {
		processingTTL = 15 * time.Minute
	}

	now := s.now().UTC()
	expiresAt := now.Add(processingTTL)
	record := transient.QueueIdempotencyRecord{
		IdempotencyKey: storeKey,
		State:          idempotencyStateProcessing,
		ExpiresAt:      expiresAt,
	}
	create := s.db.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(&record)
	if create.Error != nil {
		return "", create.Error
	}
	if create.RowsAffected == 1 {
		return queue.IdempotencyStateAcquired, nil
	}

	existing, err := s.load(ctx, storeKey)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		retry := s.db.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(&record)
		if retry.Error != nil {
			return "", retry.Error
		}
		if retry.RowsAffected == 1 {
			return queue.IdempotencyStateAcquired, nil
		}
		return queue.IdempotencyStateInProgress, nil
	}
	if err != nil {
		return "", err
	}

	if !existing.ExpiresAt.After(now) {
		update := s.db.WithContext(ctx).
			Model(&transient.QueueIdempotencyRecord{}).
			Where("idempotency_key = ? AND expires_at <= ?", storeKey, now).
			Updates(map[string]any{
				"state":      idempotencyStateProcessing,
				"expires_at": expiresAt,
				"updated_at": now,
			})
		if update.Error != nil {
			return "", update.Error
		}
		if update.RowsAffected == 1 {
			return queue.IdempotencyStateAcquired, nil
		}
		existing, err = s.load(ctx, storeKey)
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return queue.IdempotencyStateAcquired, nil
		}
		if err != nil {
			return "", err
		}
	}

	if existing.State == idempotencyStateDone {
		return queue.IdempotencyStateDuplicate, nil
	}
	return queue.IdempotencyStateInProgress, nil
}

func (s *QueueIdempotencyStore) MarkCompleted(ctx context.Context, key string, ttl time.Duration) error {
	if s == nil || s.db == nil {
		return errors.New("queue idempotency db is required")
	}
	storeKey := s.key(key)
	if storeKey == "" {
		return errors.New("queue idempotency key is required")
	}
	if ttl <= 0 {
		ttl = 24 * time.Hour
	}
	now := s.now().UTC()
	record := transient.QueueIdempotencyRecord{
		IdempotencyKey: storeKey,
		State:          idempotencyStateDone,
		ExpiresAt:      now.Add(ttl),
	}
	return s.db.WithContext(ctx).Clauses(clause.OnConflict{
		Columns: []clause.Column{{Name: "idempotency_key"}},
		DoUpdates: clause.Assignments(map[string]any{
			"state":      record.State,
			"expires_at": record.ExpiresAt,
			"updated_at": now,
		}),
	}).Create(&record).Error
}

func (s *QueueIdempotencyStore) Release(ctx context.Context, key string) error {
	if s == nil || s.db == nil {
		return errors.New("queue idempotency db is required")
	}
	storeKey := s.key(key)
	if storeKey == "" {
		return nil
	}
	return s.db.WithContext(ctx).Delete(&transient.QueueIdempotencyRecord{}, "idempotency_key = ?", storeKey).Error
}

func (s *QueueIdempotencyStore) load(ctx context.Context, key string) (*transient.QueueIdempotencyRecord, error) {
	var record transient.QueueIdempotencyRecord
	if err := s.db.WithContext(ctx).Where("idempotency_key = ?", key).First(&record).Error; err != nil {
		return nil, err
	}
	return &record, nil
}

func (s *QueueIdempotencyStore) key(key string) string {
	key = strings.TrimSpace(key)
	if key == "" {
		return ""
	}
	return s.prefix + key
}
