package initialize

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"
	"unicode"

	"kra/internal/config"
)

type configurationEnvelope struct {
	Data  json.RawMessage `json:"data"`
	Admin json.RawMessage `json:"admin"`
	Email json.RawMessage `json:"email"`
}

func (r *Repo) ConfigurationJSON() (json.RawMessage, error) {
	current := r.Config()
	if current == nil {
		current = &config.Config{}
	}
	safe := config.Clone(current)
	maskConfigSecrets(safe)
	return json.Marshal(map[string]any{"config": managementConfig(safe)})
}

func (r *Repo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) error {
	current := r.Config()
	if current == nil {
		current = &config.Config{}
	}
	next := config.Clone(current)
	var value configurationEnvelope
	if err := json.Unmarshal(raw, &value); err != nil {
		return err
	}
	if next.Data == nil {
		next.Data = &config.Data{}
	}
	if next.Admin == nil {
		next.Admin = &config.Admin{}
	}
	if err := mergeDataJSON(value.Data, &next.Data); err != nil {
		return err
	}
	if err := mergeAdminJSON(value.Admin, &next.Admin); err != nil {
		return err
	}
	if len(value.Email) > 0 && string(value.Email) != "null" {
		if err := mergeEmailJSON(value.Email, &next.Admin.Email); err != nil {
			return err
		}
	}
	preserveConfigSecrets(next, current)
	if next.Admin != nil && current.Admin != nil {
		next.Admin.ConfigPath = current.Admin.ConfigPath
	}
	// The data backend rebuilds every DSN from the structured fields while
	// preserving a standalone Source, so no pre-clearing is needed here.
	return r.PersistRuntimeConfig(ctx, next)
}

func (r *Repo) DiskMountPoints() []string {
	current := r.Config()
	if current == nil || current.Admin == nil {
		return nil
	}
	points := make([]string, 0, len(current.Admin.DiskList))
	for _, item := range current.Admin.DiskList {
		if item != nil && item.MountPoint != "" {
			points = append(points, item.MountPoint)
		}
	}
	return points
}

func mergeJSON(raw json.RawMessage, target any) error {
	if len(raw) == 0 || string(raw) == "null" {
		return nil
	}
	return json.Unmarshal(raw, target)
}

func mergeDataJSON(raw json.RawMessage, target **config.Data) error {
	return mergeJSONMap(raw, target, func(values map[string]any) error {
		if err := normalizeRedisDurations(jsonObject(values["redis"])); err != nil {
			return err
		}
		items, _ := values["redis_list"].([]any)
		for _, item := range items {
			if err := normalizeRedisDurations(jsonObject(item)); err != nil {
				return err
			}
		}
		return nil
	})
}

func normalizeRedisDurations(values map[string]any) error {
	if err := normalizeDuration(values, "read_timeout"); err != nil {
		return err
	}
	return normalizeDuration(values, "write_timeout")
}

// The admin page posts the camelCase shape managementConfig produced, so only
// the duration strings still need converting once mergeJSONMap has restored the
// snake_case key names the configuration structs declare.
func mergeAdminJSON(raw json.RawMessage, target **config.Admin) error {
	return mergeJSONMap(raw, target, func(values map[string]any) error {
		jwt := jsonObject(values["jwt"])
		if err := normalizeDuration(jwt, "expires_time"); err != nil {
			return err
		}
		if err := normalizeDuration(jwt, "buffer_time"); err != nil {
			return err
		}
		return normalizeDuration(jsonObject(values["captcha"]), "store_expiration")
	})
}

func mergeEmailJSON(raw json.RawMessage, target **config.Email) error {
	return mergeJSONMap(raw, target, func(values map[string]any) error {
		// Two legacy dashed keys the generic snake_case pass cannot derive.
		moveJSONKey(values, "is-ssl", "is_ssl")
		moveJSONKey(values, "is-loginauth", "is_login_auth")
		return nil
	})
}

func mergeJSONMap(raw json.RawMessage, target any, transform func(map[string]any) error) error {
	if len(raw) == 0 || string(raw) == "null" {
		return nil
	}
	var values map[string]any
	if err := json.Unmarshal(raw, &values); err != nil {
		return err
	}
	snakeCaseKeys(values)
	if transform != nil {
		if err := transform(values); err != nil {
			return err
		}
	}
	normalized, err := json.Marshal(values)
	if err != nil {
		return err
	}
	return mergeJSON(normalized, target)
}

// snakeCaseKeys rewrites every camelCase key in the decoded payload to the
// snake_case name the configuration structs declare. One recursive pass replaces
// a hand-written rename list that had to grow with every new setting — and that
// silently dropped whole sections when one of them was missed.
func snakeCaseKeys(value any) {
	switch typed := value.(type) {
	case map[string]any:
		renamed := map[string]any{}
		for key, item := range typed {
			snakeCaseKeys(item)
			if snake := snakeCase(key); snake != key {
				renamed[snake] = item
				delete(typed, key)
			}
		}
		for key, item := range renamed {
			typed[key] = item
		}
	case []any:
		for _, item := range typed {
			snakeCaseKeys(item)
		}
	}
}

func snakeCase(key string) string {
	var builder strings.Builder
	for index, symbol := range key {
		if !unicode.IsUpper(symbol) {
			builder.WriteRune(symbol)
			continue
		}
		if index > 0 {
			builder.WriteByte('_')
		}
		builder.WriteRune(unicode.ToLower(symbol))
	}
	return builder.String()
}

func jsonObject(value any) map[string]any {
	result, _ := value.(map[string]any)
	return result
}

// A nil map reads as absent, so callers can pass jsonObject(...) straight in.
func normalizeDuration(values map[string]any, key string) error {
	text, ok := values[key].(string)
	if !ok {
		return nil
	}
	value, err := time.ParseDuration(text)
	if err != nil {
		return fmt.Errorf("invalid duration %q: %w", text, err)
	}
	values[key] = int64(value)
	return nil
}

func moveJSONKey(values map[string]any, oldKey, newKey string) {
	if value, ok := values[oldKey]; ok {
		values[newKey] = value
		delete(values, oldKey)
	}
}

func managementConfig(value *config.Config) map[string]any {
	result := map[string]any{"data": map[string]any{}, "admin": map[string]any{}, "email": map[string]any{}}
	if value == nil {
		return result
	}
	if value.Data != nil {
		result["data"] = managementData(value.Data)
	}
	if value.Admin != nil {
		admin := value.Admin
		result["admin"] = map[string]any{
			"routerPrefix": admin.RouterPrefix,
			"jwt":          managementJWT(admin.JWT),
			"captcha":      managementCaptcha(admin.Captcha),
			"local":        managementLocal(admin.Local),
			"media":        managementMedia(admin.Media),
			"system":       managementSystem(admin.System),
			"storage":      admin.Storage,
			"disk_list":    admin.DiskList,
			"zap":          admin.Zap,
			"cors":         admin.CORS,
			"app":          admin.App,
		}
		if admin.Email != nil {
			result["email"] = map[string]any{
				"to": admin.Email.To, "from": admin.Email.From, "host": admin.Email.Host,
				"secret": admin.Email.Secret, "nickname": admin.Email.Nickname, "port": admin.Email.Port,
				"is-ssl": admin.Email.IsSSL, "is-loginauth": admin.Email.IsLoginAuth,
			}
		}
	}
	return result
}

func managementData(value *config.Data) any {
	if value == nil {
		return map[string]any{}
	}
	raw, err := json.Marshal(value)
	if err != nil {
		return value
	}
	var result map[string]any
	if json.Unmarshal(raw, &result) != nil {
		return value
	}
	if redis := jsonObject(result["redis"]); redis != nil {
		redis["read_timeout"] = value.Redis.ReadTimeout.String()
		redis["write_timeout"] = value.Redis.WriteTimeout.String()
	}
	if items, ok := result["redis_list"].([]any); ok {
		for index, item := range items {
			if index >= len(value.RedisList) || value.RedisList[index] == nil {
				continue
			}
			if redis := jsonObject(item); redis != nil {
				redis["read_timeout"] = value.RedisList[index].ReadTimeout.String()
				redis["write_timeout"] = value.RedisList[index].WriteTimeout.String()
			}
		}
	}
	return result
}

func managementJWT(value *config.JWT) any {
	if value == nil {
		return map[string]any{}
	}
	return map[string]any{"signingKey": value.SigningKey, "expiresTime": value.ExpiresTime.String(), "bufferTime": value.BufferTime.String(), "issuer": value.Issuer}
}
func managementCaptcha(value *config.Captcha) any {
	if value == nil {
		return map[string]any{}
	}
	return map[string]any{"keyLong": value.KeyLong, "imgWidth": value.ImgWidth, "imgHeight": value.ImgHeight, "storeExpiration": value.StoreExpiration.String()}
}
func managementLocal(value *config.Local) any {
	if value == nil {
		return map[string]any{}
	}
	return map[string]any{"storePath": value.StorePath, "pathPrefix": value.PathPrefix}
}
func managementMedia(value *config.Media) any {
	if value == nil {
		return map[string]any{}
	}
	chunkDir := value.ChunkDir
	if chunkDir == "" {
		chunkDir = "uploads/chunks"
	}
	return map[string]any{"sessionTtl": value.SessionTTL, "maxFileSize": value.MaxFileSize, "chunkDir": chunkDir}
}
func managementSystem(value *config.System) any {
	if value == nil {
		return map[string]any{}
	}
	return map[string]any{"useRedis": value.UseRedis, "useMultipoint": value.UseMultipoint, "useStrictAuth": value.UseStrictAuth, "disableAutoMigrate": value.DisableAutoMigrate, "useMongo": value.UseMongo, "addr": value.Addr, "iplimitCount": value.IplimitCount, "iplimitTime": value.IplimitTime}
}

func maskConfigSecrets(value *config.Config) {
	if value == nil {
		return
	}
	maskDataSecrets(value.Data)
	if value.Admin == nil {
		return
	}
	if value.Admin.JWT != nil && value.Admin.JWT.SigningKey != "" {
		value.Admin.JWT.SigningKey = config.MaskedSecret
	}
	if value.Admin.Email != nil && value.Admin.Email.Secret != "" {
		value.Admin.Email.Secret = config.MaskedSecret
	}
	config.MaskStorageSecrets(value.Admin.Storage)
}

func maskDataSecrets(value *config.Data) {
	if value == nil {
		return
	}
	if value.Database != nil {
		if value.Database.Password != "" {
			value.Database.Password = config.MaskedSecret
		}
		value.Database.Source = ""
	}
	if value.Redis != nil {
		if value.Redis.Password != "" {
			value.Redis.Password = config.MaskedSecret
		}
	}
	if value.Mongo != nil {
		if value.Mongo.Password != "" {
			value.Mongo.Password = config.MaskedSecret
		}
	}
	for _, item := range value.DatabaseList {
		if item != nil {
			if item.Password != "" {
				item.Password = config.MaskedSecret
			}
			item.Source = ""
		}
	}
	for _, item := range value.RedisList {
		if item != nil {
			if item.Password != "" {
				item.Password = config.MaskedSecret
			}
		}
	}
}

func preserveConfigSecrets(next, current *config.Config) {
	if next == nil || current == nil {
		return
	}
	preserveDataSecrets(next.Data, current.Data)
	if next.Admin == nil || current.Admin == nil {
		return
	}
	if next.Admin.JWT != nil && current.Admin.JWT != nil && config.IsMaskedSecret(next.Admin.JWT.SigningKey) {
		next.Admin.JWT.SigningKey = current.Admin.JWT.SigningKey
	}
	if next.Admin.Email != nil && current.Admin.Email != nil && config.IsMaskedSecret(next.Admin.Email.Secret) {
		next.Admin.Email.Secret = current.Admin.Email.Secret
	}
	preserveStorageSecrets(next.Admin.Storage, current.Admin.Storage)
}

func preserveDataSecrets(next, current *config.Data) {
	if next == nil || current == nil {
		return
	}
	if next.Database != nil && current.Database != nil {
		if config.IsMaskedSecret(next.Database.Password) {
			next.Database.Password = current.Database.Password
		}
		if next.Database.Source == "" {
			next.Database.Source = current.Database.Source
		}
	}
	if next.Redis != nil && current.Redis != nil && config.IsMaskedSecret(next.Redis.Password) {
		next.Redis.Password = current.Redis.Password
	}
	if next.Mongo != nil && current.Mongo != nil && config.IsMaskedSecret(next.Mongo.Password) {
		next.Mongo.Password = current.Mongo.Password
	}
	preserveDatabaseListSecrets(next.DatabaseList, current.DatabaseList)
	preserveRedisListSecrets(next.RedisList, current.RedisList)
}

func preserveDatabaseListSecrets(next, current []*config.Database) {
	byName := make(map[string]*config.Database, len(current))
	for _, item := range current {
		if item != nil && item.AliasName != "" {
			byName[item.AliasName] = item
		}
	}
	for _, item := range next {
		if item == nil {
			continue
		}
		previous := byName[item.AliasName]
		if previous == nil {
			continue
		}
		if config.IsMaskedSecret(item.Password) {
			item.Password = previous.Password
		}
		if item.Source == "" {
			item.Source = previous.Source
		}
	}
}

func preserveRedisListSecrets(next, current []*config.Redis) {
	byName := make(map[string]*config.Redis, len(current))
	for _, item := range current {
		if item != nil && item.Name != "" {
			byName[item.Name] = item
		}
	}
	for index, item := range next {
		if item == nil {
			continue
		}
		previous := byName[item.Name]
		if previous == nil && index < len(current) {
			previous = current[index]
		}
		if previous != nil && config.IsMaskedSecret(item.Password) {
			item.Password = previous.Password
		}
	}
}

func preserveStorageSecrets(next, current *config.Storage) {
	if next == nil || current == nil {
		return
	}
	if next.Qiniu != nil && current.Qiniu != nil && config.IsMaskedSecret(next.Qiniu.SecretKey) {
		next.Qiniu.SecretKey = current.Qiniu.SecretKey
	}
	nextItems, currentItems := config.ObjectStores(next), config.ObjectStores(current)
	for index := range nextItems {
		if nextItems[index] != nil && currentItems[index] != nil && config.IsMaskedSecret(nextItems[index].SecretKey) {
			nextItems[index].SecretKey = currentItems[index].SecretKey
		}
	}
}
