// Package config provides TUI configuration management
package config

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/jfraeys/fetch_ml/internal/auth"
	utils "github.com/jfraeys/fetch_ml/internal/config"
)

// CLIConfig represents the TOML config structure used by the CLI
type CLIConfig struct {
	WorkerHost string `toml:"worker_host"`
	WorkerUser string `toml:"worker_user"`
	WorkerBase string `toml:"worker_base"`
	WorkerPort int    `toml:"worker_port"`
	APIKey     string `toml:"api_key"`

	// User context (filled after authentication)
	CurrentUser *UserContext `toml:"-"`
}

// UserContext represents the authenticated user information
type UserContext struct {
	Name        string          `json:"name"`
	Admin       bool            `json:"admin"`
	Roles       []string        `json:"roles"`
	Permissions map[string]bool `json:"permissions"`
}

// LoadCLIConfig loads the CLI's TOML configuration from the provided path.
// If path is empty, ~/.ml/config.toml is used. The resolved path is returned.
// Environment variables with FETCH_ML_CLI_ prefix override config file values.
func LoadCLIConfig(configPath string) (*CLIConfig, string, error) {
	if configPath == "" {
		home, err := os.UserHomeDir()
		if err != nil {
			return nil, "", fmt.Errorf("failed to get home directory: %w", err)
		}
		configPath = filepath.Join(home, ".ml", "config.toml")
	} else {
		configPath = utils.ExpandPath(configPath)
		if !filepath.IsAbs(configPath) {
			if abs, err := filepath.Abs(configPath); err == nil {
				configPath = abs
			}
		}
	}

	// Check if TOML config exists
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		return nil, configPath, fmt.Errorf("CLI config not found at %s (run 'ml init' first)", configPath)
	} else if err != nil {
		return nil, configPath, fmt.Errorf("cannot access CLI config %s: %w", configPath, err)
	}

	if err := auth.CheckConfigFilePermissions(configPath); err != nil {
		log.Printf("Warning: %v", err)
	}

	//nolint:gosec // G304: Config path is user-controlled but trusted
	data, err := os.ReadFile(configPath)
	if err != nil {
		return nil, configPath, fmt.Errorf("failed to read CLI config: %w", err)
	}

	config := &CLIConfig{}
	parseTOML(data, config)

	if err := config.Validate(); err != nil {
		return nil, configPath, err
	}

	// Apply environment variable overrides with FETCH_ML_CLI_ prefix
	if host := os.Getenv("FETCH_ML_CLI_HOST"); host != "" {
		config.WorkerHost = host
	}
	if user := os.Getenv("FETCH_ML_CLI_USER"); user != "" {
		config.WorkerUser = user
	}
	if base := os.Getenv("FETCH_ML_CLI_BASE"); base != "" {
		config.WorkerBase = base
	}
	if port := os.Getenv("FETCH_ML_CLI_PORT"); port != "" {
		if p, err := parseInt(port); err == nil {
			config.WorkerPort = p
		}
	}
	if apiKey := os.Getenv("FETCH_ML_CLI_API_KEY"); apiKey != "" {
		config.APIKey = apiKey
	}

	return config, configPath, nil
}

// parseTOML is a simple TOML parser for the CLI config format
func parseTOML(data []byte, config *CLIConfig) {
	lines := strings.Split(string(data), "\n")

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue
		}

		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		// Remove quotes if present
		if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
			value = value[1 : len(value)-1]
		}

		switch key {
		case "worker_host":
			config.WorkerHost = value
		case "worker_user":
			config.WorkerUser = value
		case "worker_base":
			config.WorkerBase = value
		case "worker_port":
			if p, err := parseInt(value); err == nil {
				config.WorkerPort = p
			}
		case "api_key":
			config.APIKey = value
		}
	}
}

// ToTUIConfig converts CLI config to TUI config structure
func (c *CLIConfig) ToTUIConfig() (*Config, error) {
	// Get smart defaults for current environment
	smart := utils.GetSmartDefaults()

	// Set defaults for TUI-specific fields using smart defaults
	redisAddr, err := smart.RedisAddr()
	if err != nil {
		return nil, fmt.Errorf("failed to get default redis address: %w", err)
	}

	knownHosts, err := smart.KnownHostsPath()
	if err != nil {
		return nil, fmt.Errorf("failed to get default known hosts path: %w", err)
	}

	tuiConfig := &Config{
		Host:     c.WorkerHost,
		User:     c.WorkerUser,
		Port:     c.WorkerPort,
		BasePath: c.WorkerBase,

		// Set defaults for TUI-specific fields using smart defaults
		RedisAddr:          redisAddr,
		RedisDB:            0,
		PodmanImage:        "ml-worker:latest",
		ContainerWorkspace: utils.DefaultContainerWorkspace,
		ContainerResults:   utils.DefaultContainerResults,
		GPUDevices:         nil,
		KnownHosts:         knownHosts,
	}

	return tuiConfig, nil
}

// Validate validates the CLI config
func (c *CLIConfig) Validate() error {
	var errors []string

	if c.WorkerHost == "" {
		errors = append(errors, "worker_host is required")
	} else if len(strings.TrimSpace(c.WorkerHost)) == 0 {
		errors = append(errors, "worker_host cannot be empty or whitespace")
	}

	if c.WorkerUser == "" {
		errors = append(errors, "worker_user is required")
	} else if len(strings.TrimSpace(c.WorkerUser)) == 0 {
		errors = append(errors, "worker_user cannot be empty or whitespace")
	}

	if c.WorkerBase == "" {
		errors = append(errors, "worker_base is required")
	} else {
		// Expand and validate path
		c.WorkerBase = utils.ExpandPath(c.WorkerBase)
		if !filepath.IsAbs(c.WorkerBase) {
			errors = append(errors, "worker_base must be an absolute path")
		}
	}

	if c.WorkerPort == 0 {
		errors = append(errors, "worker_port is required")
	} else if err := utils.ValidatePort(c.WorkerPort); err != nil {
		errors = append(errors, fmt.Sprintf("invalid worker_port: %v", err))
	}

	if c.APIKey == "" {
		errors = append(errors, "api_key is required")
	} else if len(c.APIKey) < 16 {
		errors = append(errors, "api_key must be at least 16 characters")
	}

	if len(errors) > 0 {
		return fmt.Errorf("validation failed: %s", strings.Join(errors, "; "))
	}

	return nil
}

// AuthenticateWithServer validates the API key and sets user context
func (c *CLIConfig) AuthenticateWithServer() error {
	if c.APIKey == "" {
		return fmt.Errorf("no API key configured")
	}

	// Create temporary auth config for validation
	authConfig := &auth.Config{
		Enabled: true,
		APIKeys: map[auth.Username]auth.APIKeyEntry{
			"temp": {
				Hash:  auth.APIKeyHash(auth.HashAPIKey(c.APIKey)),
				Admin: false,
			},
		},
	}

	// Validate API key and get user info
	user, err := authConfig.ValidateAPIKey(c.APIKey)
	if err != nil {
		return fmt.Errorf("API key validation failed: %w", err)
	}

	// Set user context
	c.CurrentUser = &UserContext{
		Name:        user.Name,
		Admin:       user.Admin,
		Roles:       user.Roles,
		Permissions: user.Permissions,
	}

	return nil
}

// CheckPermission checks if the current user has a specific permission
func (c *CLIConfig) CheckPermission(permission string) bool {
	if c.CurrentUser == nil {
		return false
	}

	// Admin users have all permissions
	if c.CurrentUser.Admin {
		return true
	}

	// Check explicit permission
	if c.CurrentUser.Permissions[permission] {
		return true
	}

	// Check wildcard permission
	if c.CurrentUser.Permissions["*"] {
		return true
	}

	return false
}

// CanViewJob checks if user can view a specific job
func (c *CLIConfig) CanViewJob(jobUserID string) bool {
	if c.CurrentUser == nil {
		return false
	}

	// Admin can view all jobs
	if c.CurrentUser.Admin {
		return true
	}

	// Users can view their own jobs
	return jobUserID == c.CurrentUser.Name
}

// CanModifyJob checks if user can modify a specific job
func (c *CLIConfig) CanModifyJob(jobUserID string) bool {
	if c.CurrentUser == nil {
		return false
	}

	// Need jobs:update permission
	if !c.CheckPermission("jobs:update") {
		return false
	}

	// Admin can modify all jobs
	if c.CurrentUser.Admin {
		return true
	}

	// Users can only modify their own jobs
	return jobUserID == c.CurrentUser.Name
}

// Exists checks if a CLI configuration file exists
func Exists(configPath string) bool {
	if configPath == "" {
		home, err := os.UserHomeDir()
		if err != nil {
			return false
		}
		configPath = filepath.Join(home, ".ml", "config.toml")
	}

	_, err := os.Stat(configPath)
	return !os.IsNotExist(err)
}

// GenerateDefaultConfig creates a default TOML configuration file
func GenerateDefaultConfig(configPath string) error {
	// Create directory if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(configPath), 0750); err != nil {
		return fmt.Errorf("failed to create config directory: %w", err)
	}

	// Generate default configuration
	defaultContent := `# Fetch ML CLI Configuration
# This file contains connection settings for the ML platform

# Worker connection settings
worker_host = "localhost"        # Hostname or IP of the worker
worker_user = "your_username"     # SSH username for the worker
worker_base = "~/ml_jobs"         # Base directory for ML jobs on worker
worker_port = 22                  # SSH port (default: 22)

# Authentication
api_key = "your_api_key_here"     # Your API key (get from admin)

# Environment variable overrides:
# FETCH_ML_CLI_HOST, FETCH_ML_CLI_USER, FETCH_ML_CLI_BASE,
# FETCH_ML_CLI_PORT, FETCH_ML_CLI_API_KEY
`

	// Write configuration file
	if err := os.WriteFile(configPath, []byte(defaultContent), 0600); err != nil {
		return fmt.Errorf("failed to write config file: %w", err)
	}

	// Set proper permissions
	if err := auth.CheckConfigFilePermissions(configPath); err != nil {
		// Log permission warning but don't fail
		_ = err
	}

	return nil
}
