package logger

import (
	"fmt"
	"net/url"
	"path/filepath"
	"strings"

	"github.com/bytedance/sonic"
)

// AccessPolicy 是敏感路由的安全摘要策略。
type AccessPolicy struct {
	Name         string
	Sensitive    bool
	PresenceOnly bool
	SafeFields   map[string]struct{}
	ResultFields map[string]struct{}
	FileFields   map[string]struct{}
}

var (
	loginPolicy = AccessPolicy{
		Name: "login_token", Sensitive: true, PresenceOnly: true,
		SafeFields:   fieldSet("username", "user_id", "account_id", "success", "result_code"),
		ResultFields: fieldSet("success", "result_code", "status"),
	}
	paymentPolicy = AccessPolicy{
		Name: "payment", Sensitive: true, PresenceOnly: true,
		SafeFields:   fieldSet("order_no", "payment_no", "channel", "payment_method", "result_code", "amount", "status"),
		ResultFields: fieldSet("result_code", "status"),
	}
	wecomPolicy = AccessPolicy{
		Name: "wecom_callback", Sensitive: true,
		SafeFields: fieldSet("event_type", "resource_type", "resource_id", "result_code", "status"),
	}
	carrierCallbackPolicy = AccessPolicy{
		Name: "carrier_callback", Sensitive: true, PresenceOnly: true,
		ResultFields: fieldSet("code", "msg", "timestamp"),
	}
	configPolicy = AccessPolicy{
		Name: "sensitive_config", Sensitive: true, PresenceOnly: true,
		SafeFields:   fieldSet("config_key", "value_type", "module", "readonly", "sensitive", "configured", "status", "result_code"),
		ResultFields: fieldSet("readonly", "sensitive", "configured", "status", "result_code"),
	}
	filePolicy = AccessPolicy{
		Name: "file_export", Sensitive: true,
		SafeFields: fieldSet("content_type", "file_size", "size", "count", "task_id", "status", "result_code"),
		FileFields: fieldSet("file_name", "filename", "name"),
	}
	defaultPolicy = AccessPolicy{Name: "default_json"}
)

func fieldSet(fields ...string) map[string]struct{} {
	result := make(map[string]struct{}, len(fields))
	for _, field := range fields {
		result[field] = struct{}{}
	}
	return result
}

func policyForPath(path string) AccessPolicy {
	normalized := strings.ToLower(path)
	switch {
	case strings.Contains(normalized, "/login"), strings.Contains(normalized, "token"):
		return loginPolicy
	case strings.Contains(normalized, "/system-configs"), strings.Contains(normalized, "/wechat-configs"):
		return configPolicy
	case strings.Contains(normalized, "/callback") && (strings.Contains(normalized, "wecom") || strings.Contains(normalized, "wework")):
		return wecomPolicy
	case strings.Contains(normalized, "/callback/carriers/"):
		return carrierCallbackPolicy
	case strings.Contains(normalized, "payment"), strings.Contains(normalized, "wechat-pay"),
		strings.Contains(normalized, "alipay"), strings.Contains(normalized, "fuiou-pay"):
		return paymentPolicy
	case strings.Contains(normalized, "/storage"), strings.Contains(normalized, "/upload"),
		strings.Contains(normalized, "/download"), strings.Contains(normalized, "/export"):
		return filePolicy
	default:
		return defaultPolicy
	}
}

func sanitizeWithPolicy(raw []byte, contentType string, policy AccessPolicy) SanitizedContent {
	if len(raw) == 0 {
		return SanitizedContent{}
	}
	if !policy.Sensitive {
		return sanitizeBody(raw, BodyPolicyJSON)
	}
	summary := make(map[string]any)
	normalizedContentType := strings.ToLower(contentType)
	switch {
	case strings.Contains(normalizedContentType, "json"):
		var payload any
		if sonic.Unmarshal(raw, &payload) == nil {
			collectSafeFields(payload, policy, summary)
		}
	case strings.Contains(normalizedContentType, "x-www-form-urlencoded"):
		if values, err := url.ParseQuery(string(raw)); err == nil {
			for key, items := range values {
				collectSafeScalar(key, strings.Join(items, ","), policy, summary)
			}
		}
	}
	content := "[仅记录安全摘要]"
	if len(summary) > 0 {
		if encoded, err := sonic.Marshal(summary); err == nil {
			content, _ = truncateBody(encoded, MaxBodyLogSize)
		}
	}
	return SanitizedContent{Content: content, Size: len(raw), SHA256: digest(raw), Truncated: len(raw) > MaxBodyLogSize}
}

func collectSafeFields(value any, policy AccessPolicy, output map[string]any) {
	switch typed := value.(type) {
	case map[string]any:
		for key, item := range typed {
			switch scalar := item.(type) {
			case string:
				collectSafeScalar(key, scalar, policy, output)
			case float64, bool:
				if policy.PresenceOnly {
					if _, isResult := policy.ResultFields[strings.ToLower(key)]; isResult {
						output[key] = scalar
					} else {
						output[key] = map[string]any{"present": true, "length": len(fmt.Sprint(scalar))}
					}
				} else if _, allowed := policy.SafeFields[strings.ToLower(key)]; allowed {
					output[key] = scalar
				}
			default:
				collectSafeFields(item, policy, output)
			}
		}
	case []any:
		for _, item := range typed {
			collectSafeFields(item, policy, output)
		}
	}
}

func collectSafeScalar(key, value string, policy AccessPolicy, output map[string]any) {
	normalized := strings.ToLower(key)
	if policy.PresenceOnly {
		if _, isResult := policy.ResultFields[normalized]; isResult {
			output[key] = value
			return
		}
		output[key] = map[string]any{"present": true, "length": len(value)}
		return
	}
	if _, isFileName := policy.FileFields[normalized]; isFileName {
		base := filepath.Base(value)
		hash := digest([]byte(base))
		output[key] = "sha256:" + hash[:16]
		return
	}
	if _, allowed := policy.SafeFields[normalized]; allowed {
		output[key] = value
	}
}
