package systemconfig

import (
	"encoding/json"
	"errors"
	"strings"

	"hfb_sys/backend/internal/auditlog"
	"hfb_sys/backend/internal/model"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type Repository struct {
	db *gorm.DB
}

type AuditMeta = auditlog.Meta

type defaultConfig struct {
	Key         string
	Value       string
	Description string
}

var defaultConfigs = []defaultConfig{
	{Key: "handoff.owner_submit_timeout_minutes", Value: "30", Description: "号主待交接超时分钟数"},
	{Key: "handoff.renter_confirm_timeout_minutes", Value: "30", Description: "租客待确认收号超时分钟数"},
	{Key: "handoff.owner_return_confirm_timeout_minutes", Value: "120", Description: "号主待确认结账超时分钟数"},
	{Key: "order.pending_payment_timeout_minutes", Value: "15", Description: "订单待支付超时取消分钟数"},
	{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后结账宽限分钟数"},
	{Key: "listing.review_required", Value: "false", Description: "发布账号是否需要后台人工审核"},
	{Key: orderAgreementsConfigKey, Value: defaultOrderAgreementsConfigValue(), Description: "下单前协议配置 JSON"},
	{Key: postRentalNoticeConfigKey, Value: defaultPostRentalNoticeConfigValue(), Description: "租后须知配置 JSON"},
	{Key: "chat.default_support_admin_id", Value: "1", Description: "订单群聊回退客服 ID（仅当无可用客服时使用）"},
	{Key: "chat.auto_welcome_message", Value: "欢迎加入订单群聊！如有任何问题，请随时沟通。", Description: "建群后自动发送的欢迎话术"},
	{Key: homeAnnouncementsConfigKey, Value: defaultHomeAnnouncementsConfigValue(), Description: "移动端首页公告 JSON 数组"},
	{Key: homeBannersConfigKey, Value: defaultHomeBannersConfigValue(), Description: "移动端首页轮播图 JSON 数组"},
	{Key: publishOptionsConfigKey, Value: defaultPublishOptionsConfigValue(), Description: "发布页选项配置 JSON"},
	{Key: salePriceConfigKey, Value: defaultSalePriceConfigValue(), Description: "内部出售定价规则 JSON"},
}

var adminVisibleConfigKeys = []string{
	"chat.auto_welcome_message",
	"chat.default_support_admin_id",
	"handoff.owner_return_confirm_timeout_minutes",
	"handoff.owner_submit_timeout_minutes",
	"handoff.renter_confirm_timeout_minutes",
	"listing.publish_options",
	"listing.review_required",
	"listing.sale_price_config",
	"mobile.home_announcements",
	"mobile.home_banners",
	"order.agreements",
	"order.pending_payment_timeout_minutes",
	"order.return_overdue_grace_minutes",
	"profile.post_rental_notice",
}

func NewRepository(db *gorm.DB) *Repository {
	return &Repository{db: db}
}

func (r *Repository) List() ([]ConfigDTO, error) {
	if err := r.ensureDefaults(); err != nil {
		return nil, err
	}
	var rows []model.SystemConfig
	if err := r.db.Where("`key` IN ?", adminVisibleConfigKeys).Order("`key` ASC").Find(&rows).Error; err != nil {
		return nil, err
	}
	items := make([]ConfigDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, toDTO(row))
	}
	return items, nil
}

func (r *Repository) FindValue(key string) (string, error) {
	if err := r.ensureDefaults(); err != nil {
		return "", err
	}
	var row model.SystemConfig
	if err := r.db.Where("`key` = ?", key).First(&row).Error; err != nil {
		return "", err
	}
	return row.Value, nil
}

func (r *Repository) Update(actorID uint64, key string, req UpdateRequest, meta AuditMeta) (*ConfigDTO, error) {
	var row model.SystemConfig
	err := r.db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("`key` = ?", key).First(&row).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				row = model.SystemConfig{
					Key:         key,
					Value:       req.Value,
					Description: req.Description,
					UpdatedBy:   &actorID,
				}
				if err := tx.Create(&row).Error; err != nil {
					return err
				}
			} else {
				return err
			}
		} else {
			before := row.Value
			row.Value = req.Value
			if req.Description != "" {
				row.Description = req.Description
			}
			row.UpdatedBy = &actorID
			if err := tx.Save(&row).Error; err != nil {
				return err
			}
			if err := appendAuditLog(tx, actorID, "system_config.update", row.ID, meta, map[string]any{
				"key":    row.Key,
				"before": before,
				"after":  row.Value,
			}); err != nil {
				return err
			}
			return nil
		}
		return appendAuditLog(tx, actorID, "system_config.create", row.ID, meta, map[string]any{
			"key":   row.Key,
			"value": row.Value,
		})
	})
	if err != nil {
		return nil, err
	}
	dto := toDTO(row)
	return &dto, nil
}

func (r *Repository) ensureDefaults() error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		for _, item := range defaultConfigs {
			row := model.SystemConfig{
				Key:         item.Key,
				Value:       item.Value,
				Description: item.Description,
			}
			if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&row).Error; err != nil {
				return err
			}
			if item.Key == "chat.default_support_admin_id" {
				if err := updateDefaultDescription(tx, item); err != nil {
					return err
				}
			}
			if item.Key == publishOptionsConfigKey {
				if err := updateLegacyPublishOptions(tx, item); err != nil {
					return err
				}
			}
		}
		return nil
	})
}

func updateDefaultDescription(tx *gorm.DB, item defaultConfig) error {
	var row model.SystemConfig
	if err := tx.Where("`key` = ?", item.Key).First(&row).Error; err != nil {
		return err
	}
	if row.Description == item.Description {
		return nil
	}
	row.Description = item.Description
	return tx.Save(&row).Error
}

func updateLegacyPublishOptions(tx *gorm.DB, item defaultConfig) error {
	var row model.SystemConfig
	if err := tx.Where("`key` = ?", item.Key).First(&row).Error; err != nil {
		return err
	}
	if isLegacyPublishOptionsValue(row.Value) {
		row.Value = item.Value
		row.Description = item.Description
		return tx.Save(&row).Error
	}
	var options PublishOptionsDTO
	if err := json.Unmarshal([]byte(row.Value), &options); err != nil {
		row.Value = item.Value
		row.Description = item.Description
		return tx.Save(&row).Error
	}
	before := row.Value
	isOldPublishOptionsSchema := !strings.Contains(before, `"ban_record_options"`)
	normalizePublishOptions(&options)
	if isOldPublishOptionsSchema {
		applyConditionalBanEvidenceDefault(&options)
	}
	raw, err := json.Marshal(options)
	if err != nil {
		return err
	}
	row.Value = string(raw)
	row.Description = item.Description
	if row.Value == before {
		return nil
	}
	return tx.Save(&row).Error
}

func applyConditionalBanEvidenceDefault(options *PublishOptionsDTO) {
	defaults := DefaultPublishOptions()
	defaultSlots := make(map[string]PublishScreenshotSlot, len(defaults.ScreenshotSlots))
	for _, item := range defaults.ScreenshotSlots {
		defaultSlots[item.Key] = item
	}
	for index, item := range options.ScreenshotSlots {
		if item.Key != "tencentSecurity" {
			continue
		}
		if next, ok := defaultSlots[item.Key]; ok {
			options.ScreenshotSlots[index] = next
		}
	}
}

func isLegacyPublishOptionsValue(value string) bool {
	legacyMarkers := []string{
		`"kit5"`,
		"5级全装套",
		"6级子弹",
		"6头数量",
		"6甲数量",
		"0.26元/发",
		"0.8元/发",
		"çº",
		"å¼",
		"å…",
		"å¤",
		"ä½",
		"é«",
		"ï¼",
	}
	for _, marker := range legacyMarkers {
		if strings.Contains(value, marker) {
			return true
		}
	}
	return false
}

func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
	return auditlog.Append(tx, auditlog.Entry{
		ActorType: "admin",
		ActorID:   actorID,
		Action:    action,
		BizType:   "system_config",
		BizID:     &bizID,
		Meta:      meta,
		Detail:    detail,
	})
}

func toDTO(row model.SystemConfig) ConfigDTO {
	return ConfigDTO{
		ID:          row.ID,
		Key:         row.Key,
		Value:       row.Value,
		Description: row.Description,
		UpdatedBy:   row.UpdatedBy,
		CreatedAt:   row.CreatedAt,
		UpdatedAt:   row.UpdatedAt,
	}
}
