// Package config provides a configuration management service that handles
// loading, saving, and accessing application settings. It supports both a
// main JSON configuration file and auxiliary data stored in various formats
// like YAML, INI, and XML. The service is designed to be extensible and
// can be used with static or dynamic dependency injection.
//
// The Service struct is the core of the package, providing methods to
// interact with the configuration. It manages file paths, default values,
// and the serialization/deserialization of data.
//
// Basic usage involves creating a new Service instance and then using its
// methods to get, set, and manage configuration data.
//
// Example:
//
//	// Create a new config service.
//	cfg, err := config.New()
//	if err != nil {
//		log.Fatalf("failed to create config service: %v", err)
//	}
//
//	// Set a new value.
//	err = cfg.Set("language", "fr")
//	if err != nil {
//		log.Fatalf("failed to set config value: %v", err)
//	}
//
//	// Retrieve a value.
//	var lang string
//	err = cfg.Get("language", &lang)
//	if err != nil {
//		log.Fatalf("failed to get config value: %v", err)
//	}
//	fmt.Printf("Language: %s\n", lang)
package config

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"forge.lthn.ai/core/gui/pkg/core"
	"github.com/adrg/xdg"
)

// HandleIPCEvents processes IPC messages for the config service.
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
	switch msg.(type) {
	case core.ActionServiceStartup:
		// Config initializes during Register(), no additional startup needed.
		return nil
	default:
		if c.App != nil && c.App.Logger != nil {
			c.App.Logger.Debug("Config: Unhandled message type", "type", fmt.Sprintf("%T", msg))
		}
	}
	return nil
}

const appName = "lethean"
const configFileName = "config.json"

// Options holds configuration for the config service. This struct is provided
// for future extensibility and currently has no fields.
type Options struct{}

// Service provides access to the application's configuration.
// It handles loading, saving, and providing access to configuration values,
// abstracting away the details of file I/O and data serialization.
// The Service is designed to be a central point for all configuration-related
// operations within the application.
//
// The fields of the Service struct are automatically saved to and loaded from
// a JSON configuration file. The `json:"-"` tag on ServiceRuntime prevents
// it from being serialized.
type Service struct {
	*core.ServiceRuntime[Options] `json:"-"`

	// Persistent fields, saved to config.json.
	ConfigPath   string   `json:"configPath,omitempty"`
	UserHomeDir  string   `json:"userHomeDir,omitempty"`
	RootDir      string   `json:"rootDir,omitempty"`
	CacheDir     string   `json:"cacheDir,omitempty"`
	ConfigDir    string   `json:"configDir,omitempty"`
	DataDir      string   `json:"dataDir,omitempty"`
	WorkspaceDir string   `json:"workspaceDir,omitempty"`
	DefaultRoute string   `json:"default_route"`
	Features     []string `json:"features"`
	Language     string   `json:"language"`
}

// createServiceInstance handles the setup of the configuration service. It
// resolves necessary paths, creates directories, and loads the configuration
// file if it exists. If the configuration file is not found, it creates a new
// one with default values. This function is not exported and is used internally
// by the New and Register constructors.
func createServiceInstance() (*Service, error) {
	// --- Path and Directory Setup ---
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return nil, fmt.Errorf("could not resolve user home directory: %w", err)
	}
	userHomeDir := filepath.Join(homeDir, appName)

	rootDir, err := xdg.DataFile(appName)
	if err != nil {
		return nil, fmt.Errorf("could not resolve data directory: %w", err)
	}

	cacheDir, err := xdg.CacheFile(appName)
	if err != nil {
		return nil, fmt.Errorf("could not resolve cache directory: %w", err)
	}

	s := &Service{
		UserHomeDir:  userHomeDir,
		RootDir:      rootDir,
		CacheDir:     cacheDir,
		ConfigDir:    filepath.Join(userHomeDir, "config"),
		DataDir:      filepath.Join(userHomeDir, "data"),
		WorkspaceDir: filepath.Join(userHomeDir, "workspace"),
		DefaultRoute: "/",
		Features:     []string{},
		Language:     "en",
	}
	s.ConfigPath = filepath.Join(s.ConfigDir, configFileName)

	dirs := []string{s.RootDir, s.ConfigDir, s.DataDir, s.CacheDir, s.WorkspaceDir, s.UserHomeDir}
	for _, dir := range dirs {
		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
			return nil, fmt.Errorf("could not create directory %s: %w", dir, err)
		}
	}

	// --- Load or Create Configuration ---
	if data, err := os.ReadFile(s.ConfigPath); err == nil {
		// Config file exists, load it.
		if err := json.Unmarshal(data, s); err != nil {
			return nil, fmt.Errorf("failed to unmarshal config: %w", err)
		}
	} else if os.IsNotExist(err) {
		// Config file does not exist, create it with default values.
		if err := s.Save(); err != nil {
			return nil, fmt.Errorf("failed to create default config file: %w", err)
		}
	} else {
		// Another error occurred reading the file.
		return nil, fmt.Errorf("failed to read config file: %w", err)
	}

	return s, nil
}

// New creates a new instance of the configuration service. This constructor is
// intended for static dependency injection, where the service is created and
// managed manually. It initializes the service with default paths and values,
// and loads any existing configuration from disk.
//
// Example:
//
//	cfg, err := config.New()
//	if err != nil {
//		log.Fatalf("Failed to initialize config: %v", err)
//	}
//	// Use cfg to access configuration settings.
func New() (*Service, error) {
	return createServiceInstance()
}

// Register creates a new instance of the configuration service and registers it
// with the application's core. This constructor is intended for dynamic
// dependency injection, where services are managed by a central core component.
// It performs the same initialization as New, but also integrates the service
// with the provided core instance.
func Register(c *core.Core) (any, error) {
	s, err := createServiceInstance()
	if err != nil {
		return nil, err
	}
	// Defensive check: createServiceInstance should not return nil service with nil error
	if s == nil {
		return nil, errors.New("config: createServiceInstance returned a nil service instance with no error")
	}
	s.ServiceRuntime = core.NewServiceRuntime(c, Options{})
	return s, nil
}

// Save writes the current configuration to a JSON file. The location of the file
// is determined by the ConfigPath field of the Service struct. This method is
// typically called automatically by Set, but can be used to explicitly save
// changes.
//
// Example:
//
//	err := cfg.Save()
//	if err != nil {
//		log.Printf("Error saving configuration: %v", err)
//	}
func (s *Service) Save() error {
	data, err := json.MarshalIndent(s, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal config: %w", err)
	}

	if err := os.WriteFile(s.ConfigPath, data, 0644); err != nil {
		return fmt.Errorf("failed to write config file: %w", err)
	}
	return nil
}

// Get retrieves a configuration value by its key. The key corresponds to the
// JSON tag of a field in the Service struct. The retrieved value is stored in
// the `out` parameter, which must be a non-nil pointer to a variable of the
// correct type.
//
// Example:
//
//	var currentLanguage string
//	err := cfg.Get("language", &currentLanguage)
//	if err != nil {
//		log.Printf("Could not retrieve language setting: %v", err)
//	}
//	fmt.Println("Current language is:", currentLanguage)
func (s *Service) Get(key string, out any) error {
	val := reflect.ValueOf(s).Elem()
	typ := val.Type()

	for i := 0; i < val.NumField(); i++ {
		field := typ.Field(i)
		jsonTag := field.Tag.Get("json")
		if jsonTag != "" && jsonTag != "-" {
			jsonName := strings.Split(jsonTag, ",")[0]
			if strings.EqualFold(jsonName, key) {
				outVal := reflect.ValueOf(out)
				if outVal.Kind() != reflect.Ptr || outVal.IsNil() {
					return errors.New("output argument must be a non-nil pointer")
				}
				targetVal := outVal.Elem()
				srcVal := val.Field(i)

				if !srcVal.Type().AssignableTo(targetVal.Type()) {
					return fmt.Errorf("cannot assign config value of type %s to output of type %s", srcVal.Type(), targetVal.Type())
				}
				targetVal.Set(srcVal)
				return nil
			}
		}
	}

	return fmt.Errorf("key '%s' not found in config", key)
}

// SaveStruct saves an arbitrary struct to a JSON file in the config directory.
// This is useful for storing complex data that is not part of the main
// configuration. The `key` parameter is used as the filename (with a .json
// extension).
//
// Example:
//
//	type UserPreferences struct {
//		Theme string `json:"theme"`
//		Notifications bool `json:"notifications"`
//	}
//	prefs := UserPreferences{Theme: "dark", Notifications: true}
//	err := cfg.SaveStruct("user_prefs", prefs)
//	if err != nil {
//		log.Printf("Error saving user preferences: %v", err)
//	}
func (s *Service) SaveStruct(key string, data interface{}) error {
	filePath := filepath.Join(s.ConfigDir, key+".json")
	jsonData, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal struct for key '%s': %w", key, err)
	}
	return os.WriteFile(filePath, jsonData, 0644)
}

// LoadStruct loads an arbitrary struct from a JSON file in the config directory.
// The `key` parameter specifies the filename (without the .json extension). The
// loaded data is unmarshaled into the `data` parameter, which must be a
// non-nil pointer to a struct.
//
// Example:
//
//	var prefs UserPreferences
//	err := cfg.LoadStruct("user_prefs", &prefs)
//	if err != nil {
//		log.Printf("Error loading user preferences: %v", err)
//	}
//	fmt.Printf("User theme is: %s", prefs.Theme)
func (s *Service) LoadStruct(key string, data interface{}) error {
	filePath := filepath.Join(s.ConfigDir, key+".json")
	jsonData, err := os.ReadFile(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil // Return nil if the file doesn't exist
		}
		return fmt.Errorf("failed to read struct file for key '%s': %w", key, err)
	}
	return json.Unmarshal(jsonData, data)
}

// Set updates a configuration value and saves the change to the configuration
// file. The key corresponds to the JSON tag of a field in the Service struct.
// The provided value `v` must be of a type that is assignable to the field.
//
// Example:
//
//	err := cfg.Set("default_route", "/home")
//	if err != nil {
//		log.Printf("Failed to set default route: %v", err)
//	}
func (s *Service) Set(key string, v any) error {
	val := reflect.ValueOf(s).Elem()
	typ := val.Type()

	for i := 0; i < val.NumField(); i++ {
		field := typ.Field(i)
		jsonTag := field.Tag.Get("json")
		if jsonTag != "" && jsonTag != "-" {
			jsonName := strings.Split(jsonTag, ",")[0]
			if strings.EqualFold(jsonName, key) {
				fieldVal := val.Field(i)
				if !fieldVal.CanSet() {
					return fmt.Errorf("cannot set config field for key '%s'", key)
				}
				newVal := reflect.ValueOf(v)
				if !newVal.Type().AssignableTo(fieldVal.Type()) {
					return fmt.Errorf("type mismatch for key '%s': expected %s, got %s", key, fieldVal.Type(), newVal.Type())
				}
				fieldVal.Set(newVal)
				return s.Save()
			}
		}
	}

	return fmt.Errorf("key '%s' not found in config", key)
}

// EnableFeature enables a feature by adding it to the features list.
// If the feature is already enabled, this is a no-op.
//
// Example:
//
//	err := cfg.EnableFeature("dark_mode")
//	if err != nil {
//		log.Printf("Failed to enable feature: %v", err)
//	}
func (s *Service) EnableFeature(feature string) error {
	// Check if feature is already enabled
	for _, f := range s.Features {
		if f == feature {
			return nil // Already enabled
		}
	}
	s.Features = append(s.Features, feature)
	return s.Save()
}

// DisableFeature disables a feature by removing it from the features list.
// If the feature is not enabled, this is a no-op.
//
// Example:
//
//	err := cfg.DisableFeature("dark_mode")
//	if err != nil {
//		log.Printf("Failed to disable feature: %v", err)
//	}
func (s *Service) DisableFeature(feature string) error {
	for i, f := range s.Features {
		if f == feature {
			s.Features = append(s.Features[:i], s.Features[i+1:]...)
			return s.Save()
		}
	}
	return nil // Feature wasn't enabled, no-op
}

// IsFeatureEnabled checks if a feature is enabled.
//
// Example:
//
//	if cfg.IsFeatureEnabled("dark_mode") {
//		// Apply dark mode styles
//	}
func (s *Service) IsFeatureEnabled(feature string) bool {
	for _, f := range s.Features {
		if f == feature {
			return true
		}
	}
	return false
}
