// Package config provides a unified API for managing application configuration
// Location: monorepo/native/desktop/maplefile/internal/config/config.go
package config

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"sync"
	"time"
)

const (
	// AppNameBase is the base name of the desktop application
	AppNameBase = "maplefile"
	// AppNameDev is the app name used in development mode
	AppNameDev = "maplefile-dev"
)

// BuildMode is set at compile time via -ldflags
// Example: go build -ldflags "-X codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/config.BuildMode=dev"
// This must be set alongside the app.BuildMode for consistent behavior.
var BuildMode string

// GetAppName returns the appropriate app name based on the current mode.
// In dev mode, returns "maplefile-dev" to keep dev and production data separate.
// In production mode (or when mode is not set), returns "maplefile".
func GetAppName() string {
	mode := GetBuildMode()
	if mode == "dev" || mode == "development" {
		return AppNameDev
	}
	return AppNameBase
}

// GetBuildMode returns the current build mode from environment or compile-time variable.
// Priority: 1) Environment variable, 2) Compile-time variable, 3) Default to production
// This is used early in initialization before the full app is set up.
func GetBuildMode() string {
	// Check environment variable first
	if mode := os.Getenv("MAPLEFILE_MODE"); mode != "" {
		return mode
	}
	// Check compile-time variable
	if BuildMode != "" {
		return BuildMode
	}
	// Default to production (secure default)
	return "production"
}

// Config holds all application configuration in a flat structure
type Config struct {
	// CloudProviderAddress is the URI backend to make all calls to from this application for E2EE cloud operations.
	CloudProviderAddress string       `json:"cloud_provider_address"`
	Credentials          *Credentials `json:"credentials"`

	// Desktop-specific settings
	WindowWidth         int    `json:"window_width"`
	WindowHeight        int    `json:"window_height"`
	Theme               string `json:"theme"`                 // light, dark, auto
	Language            string `json:"language"`              // en, es, fr, etc.
	SyncMode            string `json:"sync_mode"`             // encrypted_only, hybrid, decrypted_only
	AutoSync            bool   `json:"auto_sync"`             // Enable automatic synchronization
	SyncIntervalMinutes int    `json:"sync_interval_minutes"` // Sync interval in minutes
	ShowHiddenFiles     bool   `json:"show_hidden_files"`     // Show hidden files in file manager
	DefaultView         string `json:"default_view"`          // list, grid
	SortBy              string `json:"sort_by"`               // name, date, size, type
	SortOrder           string `json:"sort_order"`            // asc, desc
}

// Credentials holds all user credentials for authentication and authorization.
// Values are decrypted for convenience purposes as we assume threat actor cannot access the decrypted values on the user's device.
type Credentials struct {
	// Email is the unique registered email of the user whom successfully logged into the system.
	Email                  string     `json:"email"`
	AccessToken            string     `json:"access_token"`
	AccessTokenExpiryTime  *time.Time `json:"access_token_expiry_time"`
	RefreshToken           string     `json:"refresh_token"`
	RefreshTokenExpiryTime *time.Time `json:"refresh_token_expiry_time"`
}

// ConfigService defines the unified interface for all configuration operations
type ConfigService interface {
	GetConfig(ctx context.Context) (*Config, error)
	GetAppDataDirPath(ctx context.Context) (string, error)
	GetCloudProviderAddress(ctx context.Context) (string, error)
	SetCloudProviderAddress(ctx context.Context, address string) error
	GetLoggedInUserCredentials(ctx context.Context) (*Credentials, error)
	SetLoggedInUserCredentials(
		ctx context.Context,
		email string,
		accessToken string,
		accessTokenExpiryTime *time.Time,
		refreshToken string,
		refreshTokenExpiryTime *time.Time,
	) error
	ClearLoggedInUserCredentials(ctx context.Context) error

	// User-specific storage methods
	// These return paths that are isolated per user and per environment (dev/production)
	GetUserDataDirPath(ctx context.Context, userEmail string) (string, error)
	GetUserFilesDirPath(ctx context.Context, userEmail string) (string, error)
	GetUserSearchIndexDir(ctx context.Context, userEmail string) (string, error)
	GetLoggedInUserEmail(ctx context.Context) (string, error)

	// Desktop-specific methods
	GetWindowSize(ctx context.Context) (width int, height int, err error)
	SetWindowSize(ctx context.Context, width int, height int) error
	GetTheme(ctx context.Context) (string, error)
	SetTheme(ctx context.Context, theme string) error
	GetLanguage(ctx context.Context) (string, error)
	SetLanguage(ctx context.Context, language string) error
	GetSyncMode(ctx context.Context) (string, error)
	SetSyncMode(ctx context.Context, mode string) error
	GetAutoSync(ctx context.Context) (bool, error)
	SetAutoSync(ctx context.Context, enabled bool) error
	GetSyncInterval(ctx context.Context) (int, error)
	SetSyncInterval(ctx context.Context, minutes int) error
	GetShowHiddenFiles(ctx context.Context) (bool, error)
	SetShowHiddenFiles(ctx context.Context, show bool) error
	GetDefaultView(ctx context.Context) (string, error)
	SetDefaultView(ctx context.Context, view string) error
	GetSortPreferences(ctx context.Context) (sortBy string, sortOrder string, err error)
	SetSortPreferences(ctx context.Context, sortBy string, sortOrder string) error
}

// repository defines the interface for loading and saving configuration
type repository interface {
	// LoadConfig loads the configuration, returning defaults if file doesn't exist
	LoadConfig(ctx context.Context) (*Config, error)

	// SaveConfig saves the configuration to persistent storage
	SaveConfig(ctx context.Context, config *Config) error
}

// configService implements the ConfigService interface
type configService struct {
	repo repository
	mu   sync.RWMutex // Thread safety
}

// fileRepository implements the repository interface with file-based storage
type fileRepository struct {
	configPath string
	appName    string
}

// New creates a new configuration service with default settings
// This is the Wire provider function
func New() (ConfigService, error) {
	appName := GetAppName()
	repo, err := newFileRepository(appName)
	if err != nil {
		return nil, err
	}

	// Wrap with integrity checking
	fileRepo := repo.(*fileRepository)
	integrityRepo, err := NewIntegrityAwareRepository(repo, appName, fileRepo.configPath)
	if err != nil {
		// Fall back to basic repository if integrity service fails
		// This allows the app to still function
		return &configService{
			repo: repo,
		}, nil
	}

	return &configService{
		repo: integrityRepo,
	}, nil
}

// NewForTesting creates a configuration service with the specified repository (for testing)
func NewForTesting(repo repository) ConfigService {
	return &configService{
		repo: repo,
	}
}

// newFileRepository creates a new instance of repository
func newFileRepository(appName string) (repository, error) {
	configDir, err := os.UserConfigDir()
	if err != nil {
		return nil, err
	}

	// Create app-specific config directory with restrictive permissions (owner only)
	// 0700 = owner read/write/execute, no access for group or others
	appConfigDir := filepath.Join(configDir, appName)
	if err := os.MkdirAll(appConfigDir, 0700); err != nil {
		return nil, err
	}

	configPath := filepath.Join(appConfigDir, "config.json")

	return &fileRepository{
		configPath: configPath,
		appName:    appName,
	}, nil
}

// LoadConfig loads the configuration from file, or returns defaults if file doesn't exist
func (r *fileRepository) LoadConfig(ctx context.Context) (*Config, error) {
	// Check if the config file exists
	if _, err := os.Stat(r.configPath); os.IsNotExist(err) {
		// Return default config if file doesn't exist
		defaults := getDefaultConfig()

		// Save the defaults for future use
		if err := r.SaveConfig(ctx, defaults); err != nil {
			return nil, err
		}

		return defaults, nil
	}

	// Read config from file
	data, err := os.ReadFile(r.configPath)
	if err != nil {
		return nil, err
	}

	var config Config
	if err := json.Unmarshal(data, &config); err != nil {
		return nil, err
	}

	return &config, nil
}

// SaveConfig saves the configuration to file with restrictive permissions
func (r *fileRepository) SaveConfig(ctx context.Context, config *Config) error {
	data, err := json.MarshalIndent(config, "", "  ")
	if err != nil {
		return err
	}

	// Use 0600 permissions (owner read/write only) for security
	return os.WriteFile(r.configPath, data, 0600)
}

// getDefaultConfig returns the default configuration values
// Note: This function is only called from LoadConfig after the config directory
// has already been created by newFileRepository, so no directory creation needed here.
func getDefaultConfig() *Config {
	return &Config{
		CloudProviderAddress: "http://localhost:8000",
		Credentials: &Credentials{
			Email:                  "",  // Leave blank because no user was authenticated.
			AccessToken:            "",  // Leave blank because no user was authenticated.
			AccessTokenExpiryTime:  nil, // Leave blank because no user was authenticated.
			RefreshToken:           "",  // Leave blank because no user was authenticated.
			RefreshTokenExpiryTime: nil, // Leave blank because no user was authenticated.
		},
		// Desktop-specific defaults
		WindowWidth:         1440,
		WindowHeight:        900,
		Theme:               "auto",
		Language:            "en",
		SyncMode:            "hybrid",
		AutoSync:            true,
		SyncIntervalMinutes: 30,
		ShowHiddenFiles:     false,
		DefaultView:         "list",
		SortBy:              "name",
		SortOrder:           "asc",
	}
}
