// Package approval 实现通用审批实例的 PostgreSQL 持久化 Adapter。
package approval

import (
	"context"
	stderrors "errors"

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

	approvaldomain "github.com/break/junhong_cmp_fiber/internal/domain/approval"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// Repository 实现通用审批实例写侧仓储。
type Repository struct {
	db *gorm.DB
}

// RepositoryProvider 为 Application 用例创建事务作用域 Repository。
type RepositoryProvider struct{}

// NewRepositoryProvider 创建通用审批 Repository Provider。
func NewRepositoryProvider() *RepositoryProvider {
	return &RepositoryProvider{}
}

// ForDB 使用指定数据库会话创建领域 Repository。
func (p *RepositoryProvider) ForDB(db *gorm.DB) approvaldomain.Repository {
	return NewRepository(db)
}

// GetForUpdate 在当前事务中锁定并读取通用审批实例。
func (r *Repository) GetForUpdate(ctx context.Context, instanceID uint) (*approvaldomain.Instance, error) {
	if r == nil || r.db == nil {
		return nil, errors.New(errors.CodeInternalError, "通用审批实例 Repository 未配置数据库会话")
	}
	var record model.ApprovalInstance
	err := r.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", instanceID).First(&record).Error
	if err == gorm.ErrRecordNotFound {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "读取通用审批实例失败")
	}
	return instanceFromModel(record), nil
}

// SaveDecision 以旧状态和旧版本为条件保存标准决策，防止并发重复终态。
func (r *Repository) SaveDecision(
	ctx context.Context,
	instance *approvaldomain.Instance,
	expectedStatus int,
	expectedVersion int,
) (bool, error) {
	if r == nil || r.db == nil || instance == nil {
		return false, errors.New(errors.CodeInternalError, "通用审批实例 Repository 未完整配置")
	}
	result := r.db.WithContext(ctx).Model(&model.ApprovalInstance{}).
		Where("id = ? AND status = ? AND version = ?", instance.ID, expectedStatus, expectedVersion).
		Updates(map[string]any{
			"status": instance.Status, "decision_snapshot": datatypes.JSON(instance.DecisionSnapshot),
			"status_changed_at": instance.StatusChangedAt, "version": instance.Version, "updated_at": instance.UpdatedAt,
		})
	if result.Error != nil {
		return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "保存通用审批决策失败")
	}
	return result.RowsAffected == 1, nil
}

func instanceFromModel(record model.ApprovalInstance) *approvaldomain.Instance {
	return &approvaldomain.Instance{
		ID: record.ID, BusinessType: record.BusinessType, BusinessID: record.BusinessID,
		SubmitterAccountID: record.SubmitterAccountID, SubmitterSnapshot: append([]byte(nil), record.SubmitterSnapshot...),
		Provider: record.Provider, ExternalRef: record.ExternalRef, Status: record.Status,
		RequestSnapshot:  append([]byte(nil), record.RequestSnapshot...),
		DecisionSnapshot: append([]byte(nil), record.DecisionSnapshot...), CorrelationID: record.CorrelationID,
		Version: record.Version, StatusChangedAt: record.StatusChangedAt,
		CreatedAt: record.CreatedAt, UpdatedAt: record.UpdatedAt,
	}
}

// NewRepository 创建通用审批实例 PostgreSQL Repository。
func NewRepository(db *gorm.DB) *Repository {
	return &Repository{db: db}
}

// Create 使用 Repository 持有的数据库会话创建一条唯一通用审批实例。
// 需要原子写入业务事实时，调用方必须以当前 GORM 事务创建 Repository。
func (r *Repository) Create(ctx context.Context, instance *approvaldomain.Instance) error {
	if r == nil || r.db == nil {
		return stderrors.New("通用审批实例 Repository 未配置数据库会话")
	}
	if instance == nil {
		return stderrors.New("通用审批实例不能为空")
	}
	record := model.ApprovalInstance{
		BusinessType: instance.BusinessType, BusinessID: instance.BusinessID,
		SubmitterAccountID: instance.SubmitterAccountID, SubmitterSnapshot: datatypes.JSON(instance.SubmitterSnapshot),
		Provider: instance.Provider, ExternalRef: instance.ExternalRef, Status: instance.Status,
		RequestSnapshot: datatypes.JSON(instance.RequestSnapshot), DecisionSnapshot: datatypes.JSON(instance.DecisionSnapshot),
		CorrelationID: instance.CorrelationID, Version: instance.Version, StatusChangedAt: instance.StatusChangedAt,
		CreatedAt: instance.CreatedAt, UpdatedAt: instance.UpdatedAt,
	}
	if err := r.db.WithContext(ctx).Create(&record).Error; err != nil {
		return err
	}
	instance.ID = record.ID
	return nil
}
