package wecom

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	stdErrors "errors"
	"strings"
	"time"

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

	systemconfigapp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig"
	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
)

// TemplateControl 是模板详情中可供业务映射的最小控件结构。
type TemplateControl struct {
	ID         string   `json:"id"`
	Type       string   `json:"type"`
	Title      string   `json:"title"`
	Required   bool     `json:"required"`
	OptionKeys []string `json:"option_keys"`
}

// TemplateDefinition 是企微 Adapter 返回的模板最小结构。
type TemplateDefinition struct {
	TemplateID string            `json:"template_id"`
	Name       string            `json:"name"`
	Controls   []TemplateControl `json:"controls"`
}

// TemplateProvider 定义审批模板详情外部端口。
type TemplateProvider interface {
	GetTemplateDetail(ctx context.Context, applicationID uint, templateID string) (TemplateDefinition, error)
}

// SceneRepository 定义审批场景当前配置持久化边界。
type SceneRepository interface {
	FindForUpdate(ctx context.Context, tx *gorm.DB, businessType string) (*model.WeComApprovalScene, error)
	Create(ctx context.Context, tx *gorm.DB, scene *model.WeComApprovalScene) error
	Update(ctx context.Context, tx *gorm.DB, scene *model.WeComApprovalScene) error
	List(ctx context.Context, page, pageSize int) ([]model.WeComApprovalScene, int64, error)
}

// SceneService 保存经企微模板详情校验的业务场景当前映射。
type SceneService struct {
	db       *gorm.DB
	provider TemplateProvider
	repo     SceneRepository
	audit    systemconfigapp.AuditWriter
	now      func() time.Time
}

// NewSceneService 创建企业微信审批场景配置用例。
func NewSceneService(db *gorm.DB, provider TemplateProvider, repo SceneRepository, audit systemconfigapp.AuditWriter) *SceneService {
	return &SceneService{db: db, provider: provider, repo: repo, audit: audit, now: time.Now}
}

// Save 校验模板控件后创建或替换指定稳定业务场景映射。
func (s *SceneService) Save(ctx context.Context, businessType string, request dto.SaveWeComApprovalSceneRequest) (*dto.WeComApprovalSceneResponse, error) {
	if s == nil || s.db == nil || s.provider == nil || s.repo == nil {
		return nil, errors.New(errors.CodeServiceUnavailable, "企业微信审批场景服务未配置")
	}
	if middleware.GetUserTypeFromContext(ctx) != constants.UserTypeSuperAdmin {
		return nil, errors.New(errors.CodeForbidden)
	}
	businessType = strings.TrimSpace(businessType)
	if !validApprovalBusinessType(businessType) {
		return nil, errors.New(errors.CodeInvalidParam, "不支持的审批业务类型")
	}
	if request.ApplicationID == 0 || strings.TrimSpace(request.TemplateID) == "" || len(request.ControlMapping) == 0 ||
		(request.Status != constants.StatusDisabled && request.Status != constants.StatusEnabled) {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	definition, err := s.provider.GetTemplateDetail(ctx, request.ApplicationID, strings.TrimSpace(request.TemplateID))
	if err != nil {
		return nil, err
	}
	if err := validateSceneMapping(businessType, request.ControlMapping, definition.Controls); err != nil {
		return nil, err
	}
	request.ControlMapping = normalizeSceneMapping(request.ControlMapping)
	mappingJSON, err := sonic.Marshal(request.ControlMapping)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "编码企业微信控件映射失败")
	}
	snapshotJSON, err := sonic.Marshal(definition)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "编码企业微信模板快照失败")
	}
	fingerprintBytes := sha256.Sum256(snapshotJSON)
	fingerprint := hex.EncodeToString(fingerprintBytes[:])
	operatorID := middleware.GetUserIDFromContext(ctx)
	if operatorID == 0 {
		return nil, errors.New(errors.CodeUnauthorized)
	}
	now := s.now().UTC()
	var saved *model.WeComApprovalScene
	err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Exec("SELECT pg_advisory_xact_lock(hashtext(?))", "wecom-scene:"+businessType).Error; err != nil {
			return err
		}
		existing, err := s.repo.FindForUpdate(ctx, tx, businessType)
		if err != nil {
			return err
		}
		before := map[string]any{"configured": false, "business_type": businessType}
		if existing == nil {
			existing = &model.WeComApprovalScene{BusinessType: businessType, CreatedBy: operatorID, CreatedAt: now}
		} else {
			before = sceneAuditSnapshot(existing)
		}
		existing.ApplicationID = request.ApplicationID
		existing.TemplateID = definition.TemplateID
		existing.TemplateName = definition.Name
		existing.ControlMapping = mappingJSON
		existing.TemplateSnapshot = snapshotJSON
		existing.TemplateFingerprint = fingerprint
		existing.Status = request.Status
		existing.LastVerifiedAt = now
		existing.UpdatedBy = operatorID
		existing.UpdatedAt = now
		if existing.ID == 0 {
			if err := s.repo.Create(ctx, tx, existing); err != nil {
				return err
			}
		} else if err := s.repo.Update(ctx, tx, existing); err != nil {
			return err
		}
		if s.audit != nil {
			requestID := ""
			if value := middleware.GetRequestIDFromContext(ctx); value != nil {
				requestID = *value
			}
			if err := s.audit.WriteConfigChange(ctx, tx, systemconfigapp.ChangeAudit{
				OperatorID: operatorID, OperationType: "wecom_approval_scene_save", Description: "保存企业微信审批模板映射",
				ConfigKey: "wecom.approval_scene." + businessType, BeforeData: before,
				AfterData: sceneAuditSnapshot(existing), RequestID: requestID, CorrelationID: requestID,
			}); err != nil {
				return err
			}
		}
		saved = existing
		return nil
	})
	if err != nil {
		var appErr *errors.AppError
		if stdErrors.As(err, &appErr) {
			return nil, appErr
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "保存企业微信审批场景失败")
	}
	return sceneResponse(*saved)
}

// List 分页查询企业微信审批场景当前配置。
func (s *SceneService) List(ctx context.Context, request dto.WeComApprovalSceneListRequest) (*dto.WeComApprovalSceneListResponse, error) {
	if s == nil || s.repo == nil {
		return nil, errors.New(errors.CodeServiceUnavailable, "企业微信审批场景服务未配置")
	}
	userType := middleware.GetUserTypeFromContext(ctx)
	if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
		return nil, errors.New(errors.CodeForbidden)
	}
	if request.Page <= 0 {
		request.Page = constants.DefaultPage
	}
	if request.PageSize <= 0 {
		request.PageSize = constants.DefaultPageSize
	}
	if request.PageSize > constants.MaxPageSize {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	scenes, total, err := s.repo.List(ctx, request.Page, request.PageSize)
	if err != nil {
		return nil, err
	}
	items := make([]dto.WeComApprovalSceneResponse, 0, len(scenes))
	for _, scene := range scenes {
		item, err := sceneResponse(scene)
		if err != nil {
			return nil, err
		}
		items = append(items, *item)
	}
	return &dto.WeComApprovalSceneListResponse{Items: items, Total: total, Page: request.Page, PageSize: request.PageSize}, nil
}

func validateSceneMapping(businessType string, mapping []dto.WeComControlMappingItem, controls []TemplateControl) error {
	controlByID := make(map[string]TemplateControl, len(controls))
	for _, control := range controls {
		controlByID[control.ID] = control
	}
	mappedControls := make(map[string]struct{}, len(mapping))
	businessFields := make(map[string]struct{}, len(mapping))
	for _, item := range mapping {
		item.BusinessField = strings.TrimSpace(item.BusinessField)
		item.ControlID = strings.TrimSpace(item.ControlID)
		if item.BusinessField == "" || item.ControlID == "" {
			return errors.New(errors.CodeInvalidParam, "企业微信控件映射字段不能为空")
		}
		if !allowedSceneBusinessField(businessType, item.BusinessField) {
			return errors.New(errors.CodeInvalidParam, "企业微信控件映射包含当前业务不支持的字段")
		}
		if _, exists := businessFields[item.BusinessField]; exists {
			return errors.New(errors.CodeInvalidParam, "企业微信业务字段映射重复")
		}
		if _, exists := mappedControls[item.ControlID]; exists {
			return errors.New(errors.CodeInvalidParam, "企业微信模板控件不能重复映射")
		}
		control, exists := controlByID[item.ControlID]
		if !exists || !strings.EqualFold(control.Type, strings.TrimSpace(item.ControlType)) {
			return errors.New(errors.CodeInvalidParam, "企业微信模板控件 ID 或类型已失效")
		}
		validOptions := make(map[string]struct{}, len(control.OptionKeys))
		for _, key := range control.OptionKeys {
			validOptions[key] = struct{}{}
		}
		for _, key := range item.OptionMapping {
			if _, exists := validOptions[key]; !exists {
				return errors.New(errors.CodeInvalidParam, "企业微信模板选择项 key 已失效")
			}
		}
		businessFields[item.BusinessField] = struct{}{}
		mappedControls[item.ControlID] = struct{}{}
	}
	for _, control := range controls {
		if control.Required {
			if _, exists := mappedControls[control.ID]; !exists {
				return errors.New(errors.CodeInvalidParam, "企业微信模板存在未映射的必填控件: "+control.Title)
			}
		}
	}
	return nil
}

func allowedSceneBusinessField(businessType, businessField string) bool {
	allowed := map[string]struct{}{}
	switch businessType {
	case constants.ApprovalBusinessTypeOfflineRecharge:
		allowed = map[string]struct{}{
			constants.ApprovalFieldRechargeNo:        {},
			constants.ApprovalFieldShopID:            {},
			constants.ApprovalFieldShopName:          {},
			constants.ApprovalFieldAmount:            {},
			constants.ApprovalFieldAmountCent:        {},
			constants.ApprovalFieldPaymentVoucherKey: {},
			constants.ApprovalFieldRemark:            {},
			constants.ApprovalFieldSubmitterID:       {},
			constants.ApprovalFieldSubmitterName:     {},
		}
	case constants.ApprovalBusinessTypeRefund:
		allowed = map[string]struct{}{
			constants.ApprovalFieldRefundNo:              {},
			constants.ApprovalFieldOrderID:               {},
			constants.ApprovalFieldOrderNo:               {},
			constants.ApprovalFieldAssetIdentifier:       {},
			constants.ApprovalFieldAssetType:             {},
			constants.ApprovalFieldActualReceivedAmount:  {},
			constants.ApprovalFieldRequestedRefundAmount: {},
			constants.ApprovalFieldRefundVoucherKey:      {},
			constants.ApprovalFieldRefundReason:          {},
			constants.ApprovalFieldPackageUsageID:        {},
			constants.ApprovalFieldSubmitterID:           {},
			constants.ApprovalFieldSubmitterName:         {},
		}
	default:
		return false
	}
	_, exists := allowed[businessField]
	return exists
}

func normalizeSceneMapping(mapping []dto.WeComControlMappingItem) []dto.WeComControlMappingItem {
	result := make([]dto.WeComControlMappingItem, 0, len(mapping))
	for _, item := range mapping {
		item.BusinessField = strings.TrimSpace(item.BusinessField)
		item.ControlID = strings.TrimSpace(item.ControlID)
		item.ControlType = strings.TrimSpace(item.ControlType)
		if item.OptionMapping == nil {
			item.OptionMapping = map[string]string{}
		}
		result = append(result, item)
	}
	return result
}

func validApprovalBusinessType(businessType string) bool {
	return businessType == constants.ApprovalBusinessTypeRefund || businessType == constants.ApprovalBusinessTypeOfflineRecharge
}

func sceneAuditSnapshot(scene *model.WeComApprovalScene) map[string]any {
	return map[string]any{
		"business_type": scene.BusinessType, "application_id": scene.ApplicationID,
		"template_id": scene.TemplateID, "status": scene.Status, "last_verified_at": scene.LastVerifiedAt,
		"template_fingerprint": scene.TemplateFingerprint,
	}
}

func sceneResponse(scene model.WeComApprovalScene) (*dto.WeComApprovalSceneResponse, error) {
	var mapping []dto.WeComControlMappingItem
	if err := sonic.Unmarshal(scene.ControlMapping, &mapping); err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "解析企业微信控件映射失败")
	}
	name := "员工线下代充值审批"
	if scene.BusinessType == constants.ApprovalBusinessTypeRefund {
		name = "退款审批"
	}
	return &dto.WeComApprovalSceneResponse{
		ID: scene.ID, BusinessType: scene.BusinessType, BusinessTypeName: name,
		ApplicationID: scene.ApplicationID, TemplateID: scene.TemplateID, TemplateName: scene.TemplateName,
		TemplateFingerprint: scene.TemplateFingerprint,
		ControlMapping:      mapping, Status: scene.Status, StatusName: constants.GetStatusName(scene.Status),
		LastVerifiedAt: scene.LastVerifiedAt, UpdatedAt: scene.UpdatedAt,
	}, nil
}
