package asset_audit

import (
	"context"
	stderrors "errors"
	"strconv"
	"strings"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
	"github.com/bytedance/sonic"
)

// OperatorInfo 审计操作者信息。
type OperatorInfo struct {
	ID   *uint
	Type string
	Name string
}

// BuildLogParams 构建资产审计日志参数。
type BuildLogParams struct {
	Operator OperatorInfo

	AssetType       string
	AssetID         uint
	AssetIdentifier string

	OperationType string
	OperationDesc string

	BeforeData map[string]any
	AfterData  map[string]any

	ResultStatus string
	ErrorCode    string
	ErrorMsg     string

	BatchTotal   int
	SuccessCount int
	FailCount    int
}

// BuildLog 统一构建资产审计日志。
func BuildLog(ctx context.Context, p BuildLogParams) *model.AssetOperationLog {
	resultStatus := strings.TrimSpace(p.ResultStatus)
	if resultStatus == "" {
		resultStatus = constants.AssetAuditResultSuccess
	}

	operationDesc := strings.TrimSpace(p.OperationDesc)
	if operationDesc == "" {
		operationDesc = p.OperationType
	}

	log := &model.AssetOperationLog{
		OperatorID:      p.Operator.ID,
		OperatorType:    strings.TrimSpace(p.Operator.Type),
		OperatorName:    strings.TrimSpace(p.Operator.Name),
		AssetType:       NormalizeAssetType(p.AssetType),
		AssetID:         p.AssetID,
		AssetIdentifier: strings.TrimSpace(p.AssetIdentifier),
		OperationType:   strings.TrimSpace(p.OperationType),
		OperationDesc:   operationDesc,
		BeforeData:      sanitizeAndTrimJSONB(p.BeforeData),
		AfterData:       sanitizeAndTrimJSONB(p.AfterData),
		RequestID:       middleware.GetRequestIDFromContext(ctx),
		IPAddress:       middleware.GetIPFromContext(ctx),
		UserAgent:       middleware.GetUserAgentFromContext(ctx),
		RequestPath:     middleware.GetRequestPathFromContext(ctx),
		RequestMethod:   middleware.GetRequestMethodFromContext(ctx),
		ResultStatus:    resultStatus,
		BatchTotal:      nonNegative(p.BatchTotal),
		SuccessCount:    nonNegative(p.SuccessCount),
		FailCount:       nonNegative(p.FailCount),
	}

	if log.OperatorType == "" {
		log.OperatorType = constants.AssetAuditOperatorTypeSystem
	}
	if log.OperatorName == "" {
		log.OperatorName = "系统任务"
	}

	if code := strings.TrimSpace(p.ErrorCode); code != "" {
		log.ErrorCode = strPtr(code)
	}
	if msg := strings.TrimSpace(p.ErrorMsg); msg != "" {
		msg = limitString(msg, 1000)
		log.ErrorMsg = strPtr(msg)
	}

	return log
}

// OperatorFromContext 从 context 解析操作者信息。
func OperatorFromContext(ctx context.Context) OperatorInfo {
	uid := middleware.GetUserIDFromContext(ctx)
	ut := middleware.GetUserTypeFromContext(ctx)
	username := strings.TrimSpace(middleware.GetUsernameFromContext(ctx))

	if uid == 0 {
		return SystemOperator("系统任务")
	}

	op := OperatorInfo{ID: uintPtr(uid)}
	switch ut {
	case constants.UserTypeSuperAdmin, constants.UserTypePlatform:
		op.Type = constants.AssetAuditOperatorTypeAdmin
		op.Name = fallbackOperatorName(username, "平台用户")
	case constants.UserTypeAgent:
		op.Type = constants.AssetAuditOperatorTypeAgent
		op.Name = fallbackOperatorName(username, "代理账号")
	case constants.UserTypeEnterprise:
		op.Type = constants.AssetAuditOperatorTypeEnterprise
		op.Name = fallbackOperatorName(username, "企业账号")
	case constants.UserTypePersonalCustomer:
		op.Type = constants.AssetAuditOperatorTypePersonal
		op.Name = fallbackOperatorName(username, "个人客户")
	default:
		op.Type = constants.AssetAuditOperatorTypeAdmin
		op.Name = fallbackOperatorName(username, "平台用户")
	}
	return op
}

// SystemOperator 构建系统操作者信息。
func SystemOperator(name string) OperatorInfo {
	n := strings.TrimSpace(name)
	if n == "" {
		n = "系统任务"
	}
	return OperatorInfo{
		ID:   nil,
		Type: constants.AssetAuditOperatorTypeSystem,
		Name: n,
	}
}

// BuildErrorInfo 从统一错误提取审计错误码与错误摘要。
func BuildErrorInfo(err error) (string, string) {
	if err == nil {
		return "", ""
	}

	var appErr *apperrors.AppError
	if strings.TrimSpace(err.Error()) == "" {
		return "", "未知错误"
	}

	if stderrors.As(err, &appErr) && appErr != nil {
		return strconv.Itoa(appErr.Code), appErr.Error()
	}

	return "", err.Error()
}

// NormalizeAssetType 统一资产类型值。
func NormalizeAssetType(assetType string) string {
	switch strings.TrimSpace(assetType) {
	case "card":
		return constants.AssetTypeIotCard
	case "device":
		return constants.AssetTypeDevice
	default:
		return strings.TrimSpace(assetType)
	}
}

func sanitizeAndTrimJSONB(data map[string]any) model.JSONB {
	if len(data) == 0 {
		return nil
	}

	sanitized := sanitizeMap(data)
	raw, err := sonic.Marshal(sanitized)
	if err != nil {
		return model.JSONB{"_marshal_error": "序列化失败"}
	}
	if len(raw) <= constants.AssetAuditDataMaxBytes {
		return model.JSONB(sanitized)
	}

	preview := string(raw)
	if len(preview) > constants.AssetAuditPreviewMaxBytes {
		preview = preview[:constants.AssetAuditPreviewMaxBytes]
	}

	return model.JSONB{
		"_truncated":      true,
		"_original_bytes": len(raw),
		"_preview":        preview,
		"_message":        "数据超过大小限制，已裁剪",
	}
}

func sanitizeMap(source map[string]any) map[string]any {
	out := make(map[string]any, len(source))
	for k, v := range source {
		if isSensitiveKey(k) {
			out[k] = "******"
			continue
		}
		out[k] = sanitizeValue(v)
	}
	return out
}

func sanitizeValue(v any) any {
	switch vv := v.(type) {
	case map[string]any:
		return sanitizeMap(vv)
	case model.JSONB:
		return sanitizeMap(map[string]any(vv))
	case []map[string]any:
		arr := make([]any, 0, len(vv))
		for _, item := range vv {
			arr = append(arr, sanitizeMap(item))
		}
		return arr
	case []any:
		arr := make([]any, 0, len(vv))
		for _, item := range vv {
			arr = append(arr, sanitizeValue(item))
		}
		return arr
	case string:
		return limitString(vv, 2000)
	default:
		return v
	}
}

func isSensitiveKey(key string) bool {
	lower := strings.ToLower(strings.TrimSpace(key))
	for _, kw := range []string{"password", "passwd", "pwd", "secret", "token", "wifi_password", "wifipwd"} {
		if strings.Contains(lower, kw) {
			return true
		}
	}
	return false
}

func nonNegative(v int) int {
	if v < 0 {
		return 0
	}
	return v
}

func limitString(value string, max int) string {
	if max <= 0 || len(value) <= max {
		return value
	}
	return value[:max]
}

func strPtr(v string) *string {
	if strings.TrimSpace(v) == "" {
		return nil
	}
	return &v
}

func uintPtr(v uint) *uint {
	return &v
}

func fallbackOperatorName(name string, fallback string) string {
	if strings.TrimSpace(name) != "" {
		return name
	}
	return fallback
}
