package wecom

import (
	"context"
	"strconv"
	"strings"
	"time"

	"github.com/bytedance/sonic"
	"gorm.io/datatypes"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// ApprovalSubmissionRecord 聚合 Worker 提交企微审批所需的本地事实。
type ApprovalSubmissionRecord struct {
	Instance model.ApprovalInstance
	Context  model.WeComApprovalContext
}

// ApprovalRecoveryRecord 是主动恢复扫描需要的本地最小事实。
type ApprovalRecoveryRecord struct {
	InstanceID            uint
	ApplicationID         uint
	TemplateID            string
	CreatorUserID         string
	SPNo                  string
	SubmissionStatus      int
	SubmissionAttemptedAt time.Time
}

// ApprovalContextRepository 管理企微提交的领取、终态和结果未知状态。
type ApprovalContextRepository struct {
	db  *gorm.DB
	now func() time.Time
}

// NewApprovalContextRepository 创建企微审批渠道上下文 Repository。
func NewApprovalContextRepository(db *gorm.DB) *ApprovalContextRepository {
	return &ApprovalContextRepository{db: db, now: time.Now}
}

// ClaimSubmission 将待提交上下文原子置为请求处理中，阻止并发或重投重复提单。
func (r *ApprovalContextRepository) ClaimSubmission(ctx context.Context, instanceID uint) (*ApprovalSubmissionRecord, bool, error) {
	if r == nil || r.db == nil || instanceID == 0 {
		return nil, false, errors.New(errors.CodeInvalidParam, "企业微信审批提交参数无效")
	}
	now := r.now().UTC()
	result := r.db.WithContext(ctx).Model(&model.WeComApprovalContext{}).
		Where("approval_instance_id = ? AND submission_status = ?", instanceID, constants.WeComSubmissionStatusReady).
		Updates(map[string]any{
			"submission_status": constants.WeComSubmissionStatusSending, "submission_attempted_at": now,
			"last_error": "", "updated_at": now,
		})
	if result.Error != nil {
		return nil, false, errors.Wrap(errors.CodeDatabaseError, result.Error, "领取企业微信审批提交任务失败")
	}
	record, err := r.Get(ctx, instanceID)
	if err != nil {
		return nil, false, err
	}
	return record, result.RowsAffected == 1, nil
}

// PromoteStaleSendingToUnknown 将超出租约的提交中记录保守转为结果未知，禁止直接重新提交。
func (r *ApprovalContextRepository) PromoteStaleSendingToUnknown(ctx context.Context, cutoff time.Time) error {
	if r == nil || r.db == nil || cutoff.IsZero() {
		return errors.New(errors.CodeInvalidParam, "企业微信审批恢复参数无效")
	}
	now := r.now().UTC()
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var stale []model.WeComApprovalContext
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("submission_status = ? AND updated_at <= ?", constants.WeComSubmissionStatusSending, cutoff.UTC()).
			Order("id ASC").Limit(constants.WeComApprovalRecoveryBatchSize).Find(&stale).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "查询超时的企业微信审批提交失败")
		}
		if len(stale) == 0 {
			return nil
		}
		instanceIDs := make([]uint, 0, len(stale))
		for _, channelContext := range stale {
			instanceIDs = append(instanceIDs, channelContext.ApprovalInstanceID)
		}
		result := tx.Model(&model.WeComApprovalContext{}).
			Where("approval_instance_id IN ? AND submission_status = ?", instanceIDs, constants.WeComSubmissionStatusSending).
			Updates(map[string]any{
				"submission_status":       constants.WeComSubmissionStatusUnknown,
				"submission_attempted_at": gorm.Expr("COALESCE(submission_attempted_at, updated_at)"),
				"last_error":              "企业微信审批提交处理中断，已进入结果未知恢复",
				"updated_at":              now,
			})
		if result.Error != nil {
			return errors.Wrap(errors.CodeDatabaseError, result.Error, "标记企业微信审批提交结果未知失败")
		}
		if err := tx.Model(&model.ApprovalInstance{}).
			Where("id IN ? AND status = ?", instanceIDs, constants.ApprovalStatusSubmitting).
			Updates(map[string]any{
				"status": constants.ApprovalStatusSubmissionUnknown, "status_changed_at": now,
				"version": gorm.Expr("version + 1"), "updated_at": now,
			}).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "同步通用审批提交结果未知状态失败")
		}
		return nil
	})
}

// ListUnknownRecovery 查询到期且尚未关联审批单号的结果未知记录。
func (r *ApprovalContextRepository) ListUnknownRecovery(ctx context.Context, cutoff time.Time, limit int) ([]ApprovalRecoveryRecord, error) {
	if limit <= 0 || limit > constants.WeComApprovalRecoveryBatchSize {
		return nil, errors.New(errors.CodeInvalidParam, "企业微信审批恢复批量大小无效")
	}
	var records []ApprovalRecoveryRecord
	err := r.db.WithContext(ctx).Table("tb_wecom_approval_context AS wc").
		Select(`wc.approval_instance_id AS instance_id, wc.application_id, wc.template_id, wc.creator_userid,
			wc.sp_no, wc.submission_status, COALESCE(wc.submission_attempted_at, wc.updated_at) AS submission_attempted_at`).
		Where("wc.submission_status = ? AND wc.sp_no = '' AND (wc.last_recovery_at IS NULL OR wc.last_recovery_at <= ?)",
			constants.WeComSubmissionStatusUnknown, cutoff.UTC()).
		Order("wc.id ASC").Limit(limit).Scan(&records).Error
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询企业微信结果未知审批失败")
	}
	return records, nil
}

// ClaimUnknownRecovery 领取一次结果未知恢复尝试，阻止多个 Worker 同时查询同一提交。
func (r *ApprovalContextRepository) ClaimUnknownRecovery(ctx context.Context, instanceID uint, cutoff time.Time) (bool, error) {
	now := r.now().UTC()
	result := r.db.WithContext(ctx).Model(&model.WeComApprovalContext{}).
		Where("approval_instance_id = ? AND submission_status = ? AND sp_no = '' AND (last_recovery_at IS NULL OR last_recovery_at <= ?)",
			instanceID, constants.WeComSubmissionStatusUnknown, cutoff.UTC()).
		UpdateColumn("last_recovery_at", now)
	if result.Error != nil {
		return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "领取企业微信结果未知恢复任务失败")
	}
	return result.RowsAffected == 1, nil
}

// ListPendingSync 查询已提交且仍未终态、需要再次拉取权威详情的审批。
func (r *ApprovalContextRepository) ListPendingSync(ctx context.Context, cutoff time.Time, limit int) ([]ApprovalRecoveryRecord, error) {
	if limit <= 0 || limit > constants.WeComApprovalRecoveryBatchSize {
		return nil, errors.New(errors.CodeInvalidParam, "企业微信审批轮询批量大小无效")
	}
	var records []ApprovalRecoveryRecord
	err := r.db.WithContext(ctx).Table("tb_wecom_approval_context AS wc").
		Select(`wc.approval_instance_id AS instance_id, wc.application_id, wc.template_id, wc.creator_userid,
			wc.sp_no, wc.submission_status, COALESCE(wc.submission_attempted_at, wc.updated_at) AS submission_attempted_at`).
		Joins("JOIN tb_approval_instance AS ai ON ai.id = wc.approval_instance_id").
		Where("wc.submission_status = ? AND wc.sp_no <> '' AND ai.status = ? AND (wc.last_synced_at IS NULL OR wc.last_synced_at <= ?)",
			constants.WeComSubmissionStatusSubmitted, constants.ApprovalStatusPending, cutoff.UTC()).
		Order("COALESCE(wc.last_synced_at, wc.created_at) ASC, wc.id ASC").Limit(limit).Scan(&records).Error
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询待轮询企业微信审批失败")
	}
	return records, nil
}

// FindSuccessfulSubmissionSPNo 从已成功的安全 Integration Log 摘要恢复本地未保存的审批单号。
func (r *ApprovalContextRepository) FindSuccessfulSubmissionSPNo(ctx context.Context, instanceID uint) (string, error) {
	resourceID := strconv.FormatUint(uint64(instanceID), 10)
	var log model.IntegrationLog
	err := r.db.WithContext(ctx).
		Where("provider = ? AND operation = ? AND direction = ? AND resource_id = ? AND result = ?",
			constants.IntegrationProviderWeCom, constants.IntegrationOperationWeComApprovalSubmit,
			constants.IntegrationDirectionOutbound, resourceID, constants.IntegrationResultSuccess).
		Order("id DESC").First(&log).Error
	if err == gorm.ErrRecordNotFound {
		return "", nil
	}
	if err != nil {
		return "", errors.Wrap(errors.CodeDatabaseError, err, "查询企业微信审批提交日志失败")
	}
	var summary struct {
		SPNo string `json:"sp_no"`
	}
	if sonic.Unmarshal(log.ResponseSummary, &summary) != nil {
		return "", nil
	}
	return strings.TrimSpace(summary.SPNo), nil
}

// ExistingSPNos 批量过滤已经关联到本地审批实例的企微审批单号。
func (r *ApprovalContextRepository) ExistingSPNos(ctx context.Context, applicationID uint, spNos []string) (map[string]struct{}, error) {
	result := make(map[string]struct{})
	if len(spNos) == 0 {
		return result, nil
	}
	var values []string
	if err := r.db.WithContext(ctx).Model(&model.WeComApprovalContext{}).
		Where("application_id = ? AND sp_no IN ?", applicationID, spNos).Pluck("sp_no", &values).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询已关联企业微信审批单号失败")
	}
	for _, value := range values {
		result[value] = struct{}{}
	}
	return result, nil
}

// FindUniqueUnknownByFingerprint 按应用、模板、发起人和提交时间唯一定位结果未知实例。
func (r *ApprovalContextRepository) FindUniqueUnknownByFingerprint(
	ctx context.Context,
	applicationID uint,
	templateID string,
	creatorUserID string,
	submittedAt time.Time,
) (*ApprovalRecoveryRecord, error) {
	if applicationID == 0 || strings.TrimSpace(templateID) == "" || strings.TrimSpace(creatorUserID) == "" || submittedAt.IsZero() {
		return nil, nil
	}
	var records []ApprovalRecoveryRecord
	err := r.db.WithContext(ctx).Table("tb_wecom_approval_context AS wc").
		Select(`wc.approval_instance_id AS instance_id, wc.application_id, wc.template_id, wc.creator_userid,
			wc.sp_no, wc.submission_status, COALESCE(wc.submission_attempted_at, wc.updated_at) AS submission_attempted_at`).
		Where(`wc.application_id = ? AND wc.template_id = ? AND wc.creator_userid = ?
			AND wc.submission_status = ? AND wc.sp_no = ''
			AND COALESCE(wc.submission_attempted_at, wc.updated_at) BETWEEN ? AND ?`,
			applicationID, strings.TrimSpace(templateID), strings.TrimSpace(creatorUserID), constants.WeComSubmissionStatusUnknown,
			submittedAt.Add(-constants.WeComApprovalRecoveryWindow).UTC(), submittedAt.Add(constants.WeComApprovalRecoveryWindow).UTC()).
		Order("wc.id ASC").Limit(2).Scan(&records).Error
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "匹配企业微信结果未知审批失败")
	}
	if len(records) != 1 {
		return nil, nil
	}
	return &records[0], nil
}

// RecoverSubmitted 将唯一确认的企微审批单号原子关联回结果未知实例。
func (r *ApprovalContextRepository) RecoverSubmitted(ctx context.Context, instanceID uint, spNo string) (bool, error) {
	spNo = strings.TrimSpace(spNo)
	if instanceID == 0 || spNo == "" {
		return false, errors.New(errors.CodeInvalidParam, "企业微信审批恢复关联参数无效")
	}
	now := r.now().UTC()
	recovered := false
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		contextResult := tx.Model(&model.WeComApprovalContext{}).
			Where("approval_instance_id = ? AND submission_status = ? AND sp_no = ''", instanceID, constants.WeComSubmissionStatusUnknown).
			Updates(map[string]any{
				"submission_status": constants.WeComSubmissionStatusSubmitted, "sp_no": spNo,
				"last_error": "", "updated_at": now,
			})
		if contextResult.Error != nil {
			return errors.Wrap(errors.CodeDatabaseError, contextResult.Error, "恢复企业微信审批单号失败")
		}
		if contextResult.RowsAffected == 0 {
			return nil
		}
		instanceResult := tx.Model(&model.ApprovalInstance{}).
			Where("id = ? AND status = ?", instanceID, constants.ApprovalStatusSubmissionUnknown).
			Updates(map[string]any{
				"external_ref": spNo, "status": constants.ApprovalStatusPending,
				"status_changed_at": now, "version": gorm.Expr("version + 1"), "updated_at": now,
			})
		if instanceResult.Error != nil {
			return errors.Wrap(errors.CodeDatabaseError, instanceResult.Error, "同步恢复后的通用审批状态失败")
		}
		if instanceResult.RowsAffected != 1 {
			return errors.New(errors.CodeConflict, "通用审批恢复状态已变化")
		}
		recovered = true
		return nil
	})
	return recovered, err
}

// Get 读取通用审批实例及对应企微渠道上下文。
func (r *ApprovalContextRepository) Get(ctx context.Context, instanceID uint) (*ApprovalSubmissionRecord, error) {
	var instance model.ApprovalInstance
	if err := r.db.WithContext(ctx).Where("id = ?", instanceID).First(&instance).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询通用审批实例失败")
	}
	var channelContext model.WeComApprovalContext
	if err := r.db.WithContext(ctx).Where("approval_instance_id = ?", instanceID).First(&channelContext).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeInvalidStatus, "企业微信审批渠道上下文不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询企业微信审批渠道上下文失败")
	}
	return &ApprovalSubmissionRecord{Instance: instance, Context: channelContext}, nil
}

// GetBySPNo 按企微审批单号读取本地通用实例和渠道上下文。
func (r *ApprovalContextRepository) GetBySPNo(ctx context.Context, applicationID uint, spNo string) (*ApprovalSubmissionRecord, error) {
	var channelContext model.WeComApprovalContext
	if err := r.db.WithContext(ctx).Where("application_id = ? AND sp_no = ?", applicationID, spNo).First(&channelContext).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询企业微信审批渠道上下文失败")
	}
	return r.Get(ctx, channelContext.ApprovalInstanceID)
}

// FindBySPNo 按企微审批单号查找本地记录；无关联时返回空，供回调区分无关审批。
func (r *ApprovalContextRepository) FindBySPNo(ctx context.Context, applicationID uint, spNo string) (*ApprovalSubmissionRecord, error) {
	var channelContext model.WeComApprovalContext
	if err := r.db.WithContext(ctx).Where("application_id = ? AND sp_no = ?", applicationID, spNo).First(&channelContext).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询企业微信审批渠道上下文失败")
	}
	return r.Get(ctx, channelContext.ApprovalInstanceID)
}

// SaveLatestDetail 保存最近一次权威企微审批详情，供终态同步和读取投影复用。
func (r *ApprovalContextRepository) SaveLatestDetail(ctx context.Context, instanceID uint, spStatus int, snapshot []byte) error {
	now := r.now().UTC()
	if err := r.db.WithContext(ctx).Model(&model.WeComApprovalContext{}).Where("approval_instance_id = ?", instanceID).Updates(map[string]any{
		"latest_sp_status": spStatus, "latest_detail_snapshot": datatypes.JSON(snapshot), "last_synced_at": now, "updated_at": now,
	}).Error; err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "保存企业微信审批详情快照失败")
	}
	return nil
}

// ReleaseForRetry 将尚未调用 applyevent 的安全失败恢复为待提交。
func (r *ApprovalContextRepository) ReleaseForRetry(ctx context.Context, instanceID uint, message string) error {
	result := r.db.WithContext(ctx).Model(&model.WeComApprovalContext{}).
		Where("approval_instance_id = ? AND submission_status = ?", instanceID, constants.WeComSubmissionStatusSending).
		Updates(map[string]any{"submission_status": constants.WeComSubmissionStatusReady, "last_error": message, "updated_at": r.now().UTC()})
	if result.Error != nil {
		return errors.Wrap(errors.CodeDatabaseError, result.Error, "恢复企业微信审批待提交状态失败")
	}
	return nil
}

// MarkSubmitted 原子保存企微 sp_no，并把通用审批实例置为审批中。
func (r *ApprovalContextRepository) MarkSubmitted(ctx context.Context, instanceID uint, spNo string) error {
	now := r.now().UTC()
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		contextResult := tx.Model(&model.WeComApprovalContext{}).
			Where("approval_instance_id = ? AND submission_status = ?", instanceID, constants.WeComSubmissionStatusSending).
			Updates(map[string]any{"submission_status": constants.WeComSubmissionStatusSubmitted, "sp_no": spNo, "last_error": "", "updated_at": now})
		if contextResult.Error != nil {
			return errors.Wrap(errors.CodeDatabaseError, contextResult.Error, "保存企业微信审批单号失败")
		}
		if contextResult.RowsAffected != 1 {
			return errors.New(errors.CodeConflict, "企业微信审批提交状态已变化")
		}
		instanceResult := tx.Model(&model.ApprovalInstance{}).
			Where("id = ? AND status = ?", instanceID, constants.ApprovalStatusSubmitting).
			Updates(map[string]any{
				"external_ref": spNo, "status": constants.ApprovalStatusPending,
				"status_changed_at": now, "version": gorm.Expr("version + 1"), "updated_at": now,
			})
		if instanceResult.Error != nil {
			return errors.Wrap(errors.CodeDatabaseError, instanceResult.Error, "更新通用审批提交状态失败")
		}
		if instanceResult.RowsAffected != 1 {
			return errors.New(errors.CodeConflict, "通用审批提交状态已变化")
		}
		return nil
	})
}

// MarkFailed 将企微明确拒绝的提交记录为提交失败。
func (r *ApprovalContextRepository) MarkFailed(ctx context.Context, instanceID uint, message string) error {
	return r.markSubmissionState(ctx, instanceID, constants.WeComSubmissionStatusFailed, constants.ApprovalStatusSubmissionFailed, message)
}

// MarkUnknown 将请求已发出但无法确认结果的提交记录为结果未知。
func (r *ApprovalContextRepository) MarkUnknown(ctx context.Context, instanceID uint, message string) error {
	return r.markSubmissionState(ctx, instanceID, constants.WeComSubmissionStatusUnknown, constants.ApprovalStatusSubmissionUnknown, message)
}

// markSubmissionState 在同一事务中同步企微渠道状态与通用审批状态，避免两侧事实分裂。
func (r *ApprovalContextRepository) markSubmissionState(ctx context.Context, instanceID uint, channelStatus, approvalStatus int, message string) error {
	now := r.now().UTC()
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Model(&model.WeComApprovalContext{}).
			Where("approval_instance_id = ? AND submission_status = ?", instanceID, constants.WeComSubmissionStatusSending).
			Updates(map[string]any{"submission_status": channelStatus, "last_error": message, "updated_at": now}).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "更新企业微信审批提交结果失败")
		}
		if err := tx.Model(&model.ApprovalInstance{}).
			Where("id = ? AND status = ?", instanceID, constants.ApprovalStatusSubmitting).
			Updates(map[string]any{
				"status": approvalStatus, "status_changed_at": now,
				"version": gorm.Expr("version + 1"), "updated_at": now,
			}).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "更新通用审批提交结果失败")
		}
		return nil
	})
}
