package localdb

import (
	"encoding/json"
	"fmt"
	"log/slog"

	"gorm.io/gorm"
)

// ConfigTypeDef describes one device configuration type as synced from qt_settings.
type ConfigTypeDef struct {
	Code         string   `json:"code"`
	NameRu       string   `json:"name_ru"`
	DisplayOrder int      `json:"display_order"`
	Categories   []string `json:"categories"`
}

// TabSection is a named sub-group of categories within a configurator tab.
type TabSection struct {
	Title      string   `json:"title"`
	Categories []string `json:"categories"`
}

// TabDef describes one tab in the configurator as synced from qt_settings.
type TabDef struct {
	Key          string       `json:"key"`
	Label        string       `json:"label"`
	SingleSelect bool         `json:"single_select"`
	Categories   []string     `json:"categories"`
	Sections     []TabSection `json:"sections,omitempty"`
}

// ConfiguratorSettings holds all four server-driven settings consumed by the configurator.
// Fields are nil/empty when the corresponding qt_settings key is absent or unparseable;
// callers are expected to apply hardcoded fallbacks in that case.
type ConfiguratorSettings struct {
	ConfigTypes        []ConfigTypeDef     `json:"config_types"`
	TabConfig          []TabDef            `json:"tab_config"`
	AlwaysVisibleTabs  []string            `json:"always_visible_tabs"`
	RequiredCategories map[string][]string `json:"required_categories"`
}

// SyncQtSettings reads all rows from qt_settings on MariaDB and replaces the
// local_qt_settings cache in a single SQLite transaction. Returns an error if
// the qt_settings table doesn't exist on the server (old server without the
// table) or on any query/write failure.
func (l *LocalDB) SyncQtSettings(mariaDB *gorm.DB) error {
	var rows []LocalQtSetting
	if err := mariaDB.
		Table("qt_settings").
		Select("name, value").
		Find(&rows).Error; err != nil {
		return fmt.Errorf("reading qt_settings from MariaDB: %w", err)
	}

	return l.db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Exec("DELETE FROM local_qt_settings").Error; err != nil {
			return fmt.Errorf("clearing local_qt_settings: %w", err)
		}
		if len(rows) == 0 {
			return nil
		}
		if err := tx.Create(&rows).Error; err != nil {
			return fmt.Errorf("inserting local_qt_settings: %w", err)
		}
		slog.Info("qt_settings synced", "count", len(rows))
		return nil
	})
}

// GetQtSetting returns the raw JSON value for a named setting.
// found is false when the key does not exist.
func (l *LocalDB) GetQtSetting(name string) (value string, found bool, err error) {
	var row LocalQtSetting
	res := l.db.Where("name = ?", name).First(&row)
	if res.Error != nil {
		if res.Error == gorm.ErrRecordNotFound {
			return "", false, nil
		}
		return "", false, res.Error
	}
	return row.Value, true, nil
}

// GetConfiguratorSettings reads all four known settings from local_qt_settings and
// parses them. Any missing or unparseable key is left as nil/zero in the result;
// the caller must apply fallbacks.
func (l *LocalDB) GetConfiguratorSettings() (*ConfiguratorSettings, error) {
	out := &ConfiguratorSettings{}

	keys := []string{"config_types", "tab_config", "always_visible_tabs", "required_categories"}
	for _, key := range keys {
		raw, found, err := l.GetQtSetting(key)
		if err != nil {
			return out, fmt.Errorf("reading setting %q: %w", key, err)
		}
		if !found || raw == "" {
			continue
		}
		switch key {
		case "config_types":
			if err := json.Unmarshal([]byte(raw), &out.ConfigTypes); err != nil {
				slog.Warn("failed to parse config_types setting", "error", err)
			}
		case "tab_config":
			if err := json.Unmarshal([]byte(raw), &out.TabConfig); err != nil {
				slog.Warn("failed to parse tab_config setting", "error", err)
			}
		case "always_visible_tabs":
			if err := json.Unmarshal([]byte(raw), &out.AlwaysVisibleTabs); err != nil {
				slog.Warn("failed to parse always_visible_tabs setting", "error", err)
			}
		case "required_categories":
			if err := json.Unmarshal([]byte(raw), &out.RequiredCategories); err != nil {
				slog.Warn("failed to parse required_categories setting", "error", err)
			}
		}
	}

	return out, nil
}
