package repository

import (
	"sort"
	"strings"
	"time"

	"hyapp-admin-server/internal/model"

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

type AppVersionListOptions struct {
	AppCode  string
	Platform string
	Keyword  string
}

type AppExploreTabListOptions struct {
	AppCode string
	Keyword string
	Enabled *bool
}

func (s *Store) ListAppConfigs(group string) ([]model.AppConfig, error) {
	var items []model.AppConfig
	// 旧的通用配置继续使用 app_code='' 基线行；H5 配置必须走 scoped API，避免覆盖行被其他旧调用方误读。
	err := s.db.Where("app_code = '' AND `group` = ?", strings.TrimSpace(group)).Order("`key` ASC").Find(&items).Error
	return items, err
}

func (s *Store) GetAppConfig(group string, key string) (model.AppConfig, error) {
	var item model.AppConfig
	err := s.db.Where("app_code = '' AND `group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).First(&item).Error
	return item, err
}

func (s *Store) CreateAppConfig(item *model.AppConfig) error {
	return s.db.Create(item).Error
}

func (s *Store) UpdateAppConfig(item *model.AppConfig) error {
	return s.db.Save(item).Error
}

func (s *Store) DeleteAppConfig(group string, key string) error {
	return s.db.Where("app_code = '' AND `group` = ? AND `key` = ?", strings.TrimSpace(group), strings.TrimSpace(key)).Delete(&model.AppConfig{}).Error
}

func (s *Store) UpsertAppConfigs(items []model.AppConfig) error {
	if len(items) == 0 {
		return nil
	}

	nowMS := time.Now().UTC().UnixMilli()
	for index := range items {
		items[index].AppCode = ""
		items[index].Group = strings.TrimSpace(items[index].Group)
		items[index].Key = strings.TrimSpace(items[index].Key)
		items[index].IsDeleted = false
		items[index].UpdatedAtMS = nowMS
		if items[index].CreatedAtMS == 0 {
			items[index].CreatedAtMS = nowMS
		}
	}

	return s.db.Clauses(clause.OnConflict{
		Columns: []clause.Column{
			{Name: "app_code"},
			{Name: "group"},
			{Name: "key"},
		},
		DoUpdates: clause.AssignmentColumns([]string{"value", "description", "is_deleted", "updated_at_ms"}),
	}).Create(&items).Error
}

// ListScopedAppConfigs 返回当前 App 的有效配置视图：scoped 行覆盖 legacy 基线，墓碑显式屏蔽基线。
// legacy 基线保持只读，因此迁移后未知的新 App 也能继续获得原配置，同时任意 App 的写操作不会污染其他 App。
func (s *Store) ListScopedAppConfigs(appCode string, group string) ([]model.AppConfig, error) {
	appCode = normalizeScopedAppCode(appCode)
	var rows []model.AppConfig
	err := s.db.
		Where("`group` = ? AND app_code IN ?", strings.TrimSpace(group), []string{"", appCode}).
		Order("`key` ASC, app_code ASC").
		Find(&rows).Error
	if err != nil {
		return nil, err
	}
	return mergeScopedAppConfigs(rows, appCode), nil
}

// GetScopedAppConfig 和列表使用同一覆盖规则，防止更新/删除误操作其他 App 或 legacy 基线行。
func (s *Store) GetScopedAppConfig(appCode string, group string, key string) (model.AppConfig, error) {
	appCode = normalizeScopedAppCode(appCode)
	var rows []model.AppConfig
	err := s.db.
		Where("`group` = ? AND `key` = ? AND app_code IN ?", strings.TrimSpace(group), strings.TrimSpace(key), []string{"", appCode}).
		Order("app_code ASC").
		Find(&rows).Error
	if err != nil {
		return model.AppConfig{}, err
	}
	items := mergeScopedAppConfigs(rows, appCode)
	if len(items) == 0 {
		return model.AppConfig{}, gorm.ErrRecordNotFound
	}
	return items[0], nil
}

// ListOwnedAppConfigs 只返回当前 App 自己持有的配置行。H5 等产品入口不能继承空 app_code
// 基线，否则新增 App 在尚未配置时会直接暴露默认 App 的运营链接，形成跨 App 内容串用。
func (s *Store) ListOwnedAppConfigs(appCode string, group string) ([]model.AppConfig, error) {
	var items []model.AppConfig
	err := s.db.
		Where("app_code = ? AND `group` = ? AND is_deleted = FALSE", normalizeScopedAppCode(appCode), strings.TrimSpace(group)).
		Order("`key` ASC").
		Find(&items).Error
	return items, err
}

// GetOwnedAppConfig 和列表保持同一强隔离语义；更新与删除不能通过 legacy 基线找到其他
// App 可见的同名 key，也不能把墓碑当成可编辑配置。
func (s *Store) GetOwnedAppConfig(appCode string, group string, key string) (model.AppConfig, error) {
	var item model.AppConfig
	err := s.db.
		Where("app_code = ? AND `group` = ? AND `key` = ? AND is_deleted = FALSE", normalizeScopedAppCode(appCode), strings.TrimSpace(group), strings.TrimSpace(key)).
		First(&item).Error
	return item, err
}

// DeleteOwnedAppConfig 物理删除当前 App 的配置。强隔离配置不存在跨 App 基线回退，因此无需保留
// 墓碑；删除后当前 App 应立即回到“未配置”状态。
func (s *Store) DeleteOwnedAppConfig(appCode string, group string, key string) error {
	return s.db.
		Where("app_code = ? AND `group` = ? AND `key` = ?", normalizeScopedAppCode(appCode), strings.TrimSpace(group), strings.TrimSpace(key)).
		Delete(&model.AppConfig{}).Error
}

// UpsertScopedAppConfigs 只写当前 app_code；调用方无法通过请求体覆盖租户作用域。
func (s *Store) UpsertScopedAppConfigs(appCode string, items []model.AppConfig) error {
	if len(items) == 0 {
		return nil
	}
	appCode = normalizeScopedAppCode(appCode)
	nowMS := time.Now().UTC().UnixMilli()
	for index := range items {
		items[index].AppCode = appCode
		items[index].Group = strings.TrimSpace(items[index].Group)
		items[index].Key = strings.TrimSpace(items[index].Key)
		items[index].IsDeleted = false
		items[index].UpdatedAtMS = nowMS
		if items[index].CreatedAtMS == 0 {
			items[index].CreatedAtMS = nowMS
		}
	}
	return s.db.Clauses(clause.OnConflict{
		Columns:   []clause.Column{{Name: "app_code"}, {Name: "group"}, {Name: "key"}},
		DoUpdates: clause.AssignmentColumns([]string{"value", "description", "is_deleted", "updated_at_ms"}),
	}).Create(&items).Error
}

// TombstoneScopedAppConfig 不物理删除 scoped 行。若配置来自 legacy 基线，墓碑会阻止它在删除后重新出现。
func (s *Store) TombstoneScopedAppConfig(appCode string, group string, key string) error {
	appCode = normalizeScopedAppCode(appCode)
	nowMS := time.Now().UTC().UnixMilli()
	item := model.AppConfig{
		AppCode:     appCode,
		Group:       strings.TrimSpace(group),
		Key:         strings.TrimSpace(key),
		IsDeleted:   true,
		CreatedAtMS: nowMS,
		UpdatedAtMS: nowMS,
	}
	return s.db.Clauses(clause.OnConflict{
		Columns: []clause.Column{{Name: "app_code"}, {Name: "group"}, {Name: "key"}},
		DoUpdates: clause.Assignments(map[string]any{
			"is_deleted":    true,
			"updated_at_ms": nowMS,
		}),
	}).Create(&item).Error
}

func normalizeScopedAppCode(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func mergeScopedAppConfigs(rows []model.AppConfig, appCode string) []model.AppConfig {
	appCode = normalizeScopedAppCode(appCode)
	baseline := make(map[string]model.AppConfig)
	overrides := make(map[string]model.AppConfig)
	for _, item := range rows {
		item.AppCode = normalizeScopedAppCode(item.AppCode)
		item.Group = strings.TrimSpace(item.Group)
		item.Key = strings.TrimSpace(item.Key)
		if item.Key == "" {
			continue
		}
		switch item.AppCode {
		case "":
			baseline[item.Key] = item
		case appCode:
			overrides[item.Key] = item
		}
	}

	keys := make(map[string]struct{}, len(baseline)+len(overrides))
	for key := range baseline {
		keys[key] = struct{}{}
	}
	for key := range overrides {
		keys[key] = struct{}{}
	}
	sortedKeys := make([]string, 0, len(keys))
	for key := range keys {
		sortedKeys = append(sortedKeys, key)
	}
	sort.Strings(sortedKeys)

	items := make([]model.AppConfig, 0, len(sortedKeys))
	for _, key := range sortedKeys {
		item, overridden := overrides[key]
		if !overridden {
			item = baseline[key]
		}
		if item.IsDeleted {
			continue
		}
		// 对外有效视图始终归属于当前 App；baseline 的空作用域不应泄漏给响应或后续写操作。
		item.AppCode = appCode
		items = append(items, item)
	}
	return items
}

func (s *Store) ListAppVersions(options AppVersionListOptions) ([]model.AppVersion, error) {
	db := s.db.Where("app_code = ?", strings.TrimSpace(options.AppCode))
	if platform := strings.TrimSpace(options.Platform); platform != "" {
		db = db.Where("platform = ?", platform)
	}
	if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
		like := "%" + keyword + "%"
		db = db.Where("version LIKE ? OR download_url LIKE ? OR description LIKE ?", like, like, like)
	}

	var items []model.AppVersion
	err := db.Order("platform ASC, build_number DESC, id DESC").Find(&items).Error
	return items, err
}

func (s *Store) GetAppVersion(appCode string, id uint) (model.AppVersion, error) {
	var item model.AppVersion
	err := s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).First(&item).Error
	return item, err
}

func (s *Store) CreateAppVersion(item *model.AppVersion) error {
	return s.db.Create(item).Error
}

func (s *Store) UpdateAppVersion(item *model.AppVersion) error {
	return s.db.Save(item).Error
}

func (s *Store) DeleteAppVersion(appCode string, id uint) error {
	return s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).Delete(&model.AppVersion{}).Error
}

func (s *Store) ListAppExploreTabs(options AppExploreTabListOptions) ([]model.AppExploreTab, error) {
	db := s.db.Where("app_code = ?", strings.TrimSpace(options.AppCode))
	if options.Enabled != nil {
		db = db.Where("enabled = ?", *options.Enabled)
	}
	if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
		like := "%" + keyword + "%"
		db = db.Where("tab LIKE ? OR h5_url LIKE ?", like, like)
	}

	var items []model.AppExploreTab
	err := db.Order("sort_order DESC, id DESC").Find(&items).Error
	return items, err
}

func (s *Store) GetAppExploreTab(appCode string, id uint) (model.AppExploreTab, error) {
	var item model.AppExploreTab
	err := s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).First(&item).Error
	return item, err
}

func (s *Store) CreateAppExploreTab(item *model.AppExploreTab) error {
	return s.db.Create(item).Error
}

func (s *Store) UpdateAppExploreTab(item *model.AppExploreTab) error {
	return s.db.Save(item).Error
}

func (s *Store) DeleteAppExploreTab(appCode string, id uint) error {
	return s.db.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).Delete(&model.AppExploreTab{}).Error
}
