// Package bugseti provides services for the BugSETI distributed bug fixing application.
package bugseti

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

// ConfigService manages application configuration and persistence.
type ConfigService struct {
	config *Config
	path   string
	mu     sync.RWMutex
}

// Config holds all BugSETI configuration.
type Config struct {
	// Authentication
	GitHubToken string `json:"githubToken,omitempty"`

	// Repositories
	WatchedRepos []string `json:"watchedRepos"`
	Labels       []string `json:"labels"`

	// Scheduling
	WorkHours     *WorkHours `json:"workHours,omitempty"`
	FetchInterval int        `json:"fetchIntervalMinutes"`

	// Notifications
	NotificationsEnabled bool `json:"notificationsEnabled"`
	NotificationSound    bool `json:"notificationSound"`

	// Workspace
	WorkspaceDir string `json:"workspaceDir,omitempty"`
	DataDir      string `json:"dataDir,omitempty"`
	// Marketplace MCP
	MarketplaceMCPRoot string `json:"marketplaceMcpRoot,omitempty"`

	// Onboarding
	Onboarded   bool      `json:"onboarded"`
	OnboardedAt time.Time `json:"onboardedAt,omitempty"`

	// UI Preferences
	Theme         string `json:"theme"`
	ShowTrayPanel bool   `json:"showTrayPanel"`

	// Advanced
	MaxConcurrentIssues int  `json:"maxConcurrentIssues"`
	AutoSeedContext     bool `json:"autoSeedContext"`

	// Updates
	UpdateChannel       string    `json:"updateChannel"`       // stable, beta, nightly
	AutoUpdate          bool      `json:"autoUpdate"`          // Automatically install updates
	UpdateCheckInterval int       `json:"updateCheckInterval"` // Check interval in hours (0 = disabled)
	LastUpdateCheck     time.Time `json:"lastUpdateCheck,omitempty"`
}

// WorkHours defines when BugSETI should actively fetch issues.
type WorkHours struct {
	Enabled   bool   `json:"enabled"`
	StartHour int    `json:"startHour"` // 0-23
	EndHour   int    `json:"endHour"`   // 0-23
	Days      []int  `json:"days"`      // 0=Sunday, 6=Saturday
	Timezone  string `json:"timezone"`
}

// NewConfigService creates a new ConfigService with default values.
func NewConfigService() *ConfigService {
	// Determine config path
	configDir, err := os.UserConfigDir()
	if err != nil {
		configDir = filepath.Join(os.Getenv("HOME"), ".config")
	}

	bugsetiDir := filepath.Join(configDir, "bugseti")
	if err := os.MkdirAll(bugsetiDir, 0755); err != nil {
		log.Printf("Warning: could not create config directory: %v", err)
	}

	return &ConfigService{
		path: filepath.Join(bugsetiDir, "config.json"),
		config: &Config{
			WatchedRepos: []string{},
			Labels: []string{
				"good first issue",
				"help wanted",
				"beginner-friendly",
			},
			FetchInterval:        15,
			NotificationsEnabled: true,
			NotificationSound:    true,
			Theme:                "dark",
			ShowTrayPanel:        true,
			MaxConcurrentIssues:  1,
			AutoSeedContext:      true,
			DataDir:              bugsetiDir,
			MarketplaceMCPRoot:   "",
			UpdateChannel:        "stable",
			AutoUpdate:           false,
			UpdateCheckInterval:  6, // Check every 6 hours
		},
	}
}

// ServiceName returns the service name for Wails.
func (c *ConfigService) ServiceName() string {
	return "ConfigService"
}

// Load reads the configuration from disk.
func (c *ConfigService) Load() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	data, err := os.ReadFile(c.path)
	if err != nil {
		if os.IsNotExist(err) {
			// No config file yet, use defaults
			return c.saveUnsafe()
		}
		return err
	}

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

	// Merge with defaults for any new fields
	c.mergeDefaults(&config)
	c.config = &config
	return nil
}

// Save persists the configuration to disk.
func (c *ConfigService) Save() error {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.saveUnsafe()
}

// saveUnsafe writes config without acquiring lock.
func (c *ConfigService) saveUnsafe() error {
	data, err := json.MarshalIndent(c.config, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(c.path, data, 0644)
}

// mergeDefaults fills in default values for any unset fields.
func (c *ConfigService) mergeDefaults(config *Config) {
	if config.Labels == nil || len(config.Labels) == 0 {
		config.Labels = c.config.Labels
	}
	if config.FetchInterval == 0 {
		config.FetchInterval = 15
	}
	if config.Theme == "" {
		config.Theme = "dark"
	}
	if config.MaxConcurrentIssues == 0 {
		config.MaxConcurrentIssues = 1
	}
	if config.DataDir == "" {
		config.DataDir = c.config.DataDir
	}
	if config.UpdateChannel == "" {
		config.UpdateChannel = "stable"
	}
	if config.UpdateCheckInterval == 0 {
		config.UpdateCheckInterval = 6
	}
}

// GetConfig returns a copy of the current configuration.
func (c *ConfigService) GetConfig() Config {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return *c.config
}

// GetMarketplaceMCPRoot returns the configured marketplace MCP root path.
func (c *ConfigService) GetMarketplaceMCPRoot() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.MarketplaceMCPRoot
}

// SetConfig updates the configuration and saves it.
func (c *ConfigService) SetConfig(config Config) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config = &config
	return c.saveUnsafe()
}

// GetWatchedRepos returns the list of watched repositories.
func (c *ConfigService) GetWatchedRepos() []string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.WatchedRepos
}

// AddWatchedRepo adds a repository to the watch list.
func (c *ConfigService) AddWatchedRepo(repo string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, r := range c.config.WatchedRepos {
		if r == repo {
			return nil // Already watching
		}
	}

	c.config.WatchedRepos = append(c.config.WatchedRepos, repo)
	return c.saveUnsafe()
}

// RemoveWatchedRepo removes a repository from the watch list.
func (c *ConfigService) RemoveWatchedRepo(repo string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for i, r := range c.config.WatchedRepos {
		if r == repo {
			c.config.WatchedRepos = append(c.config.WatchedRepos[:i], c.config.WatchedRepos[i+1:]...)
			return c.saveUnsafe()
		}
	}

	return nil
}

// GetLabels returns the issue labels to filter by.
func (c *ConfigService) GetLabels() []string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.Labels
}

// SetLabels updates the issue labels.
func (c *ConfigService) SetLabels(labels []string) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.Labels = labels
	return c.saveUnsafe()
}

// GetFetchInterval returns the fetch interval as a duration.
func (c *ConfigService) GetFetchInterval() time.Duration {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return time.Duration(c.config.FetchInterval) * time.Minute
}

// SetFetchInterval sets the fetch interval in minutes.
func (c *ConfigService) SetFetchInterval(minutes int) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.FetchInterval = minutes
	return c.saveUnsafe()
}

// IsWithinWorkHours checks if the current time is within configured work hours.
func (c *ConfigService) IsWithinWorkHours() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if c.config.WorkHours == nil || !c.config.WorkHours.Enabled {
		return true // No work hours restriction
	}

	wh := c.config.WorkHours
	now := time.Now()

	// Check timezone
	if wh.Timezone != "" {
		loc, err := time.LoadLocation(wh.Timezone)
		if err == nil {
			now = now.In(loc)
		}
	}

	// Check day
	day := int(now.Weekday())
	dayAllowed := false
	for _, d := range wh.Days {
		if d == day {
			dayAllowed = true
			break
		}
	}
	if !dayAllowed {
		return false
	}

	// Check hour
	hour := now.Hour()
	if wh.StartHour <= wh.EndHour {
		return hour >= wh.StartHour && hour < wh.EndHour
	}
	// Handle overnight (e.g., 22:00 - 06:00)
	return hour >= wh.StartHour || hour < wh.EndHour
}

// GetWorkHours returns the work hours configuration.
func (c *ConfigService) GetWorkHours() *WorkHours {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.WorkHours
}

// SetWorkHours updates the work hours configuration.
func (c *ConfigService) SetWorkHours(wh *WorkHours) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.WorkHours = wh
	return c.saveUnsafe()
}

// IsNotificationsEnabled returns whether notifications are enabled.
func (c *ConfigService) IsNotificationsEnabled() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.NotificationsEnabled
}

// SetNotificationsEnabled enables or disables notifications.
func (c *ConfigService) SetNotificationsEnabled(enabled bool) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.NotificationsEnabled = enabled
	return c.saveUnsafe()
}

// GetWorkspaceDir returns the workspace directory.
func (c *ConfigService) GetWorkspaceDir() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.WorkspaceDir
}

// SetWorkspaceDir sets the workspace directory.
func (c *ConfigService) SetWorkspaceDir(dir string) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.WorkspaceDir = dir
	return c.saveUnsafe()
}

// GetDataDir returns the data directory.
func (c *ConfigService) GetDataDir() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.DataDir
}

// IsOnboarded returns whether the user has completed onboarding.
func (c *ConfigService) IsOnboarded() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.Onboarded
}

// CompleteOnboarding marks onboarding as complete.
func (c *ConfigService) CompleteOnboarding() error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.Onboarded = true
	c.config.OnboardedAt = time.Now()
	return c.saveUnsafe()
}

// GetTheme returns the current theme.
func (c *ConfigService) GetTheme() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.Theme
}

// SetTheme sets the theme.
func (c *ConfigService) SetTheme(theme string) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.Theme = theme
	return c.saveUnsafe()
}

// IsAutoSeedEnabled returns whether automatic context seeding is enabled.
func (c *ConfigService) IsAutoSeedEnabled() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.AutoSeedContext
}

// SetAutoSeedEnabled enables or disables automatic context seeding.
func (c *ConfigService) SetAutoSeedEnabled(enabled bool) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.AutoSeedContext = enabled
	return c.saveUnsafe()
}

// UpdateSettings holds update-related configuration.
type UpdateSettings struct {
	Channel       string    `json:"channel"`
	AutoUpdate    bool      `json:"autoUpdate"`
	CheckInterval int       `json:"checkInterval"` // Hours
	LastCheck     time.Time `json:"lastCheck"`
}

// GetUpdateSettings returns the update settings.
func (c *ConfigService) GetUpdateSettings() UpdateSettings {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return UpdateSettings{
		Channel:       c.config.UpdateChannel,
		AutoUpdate:    c.config.AutoUpdate,
		CheckInterval: c.config.UpdateCheckInterval,
		LastCheck:     c.config.LastUpdateCheck,
	}
}

// SetUpdateSettings updates the update settings.
func (c *ConfigService) SetUpdateSettings(settings UpdateSettings) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.UpdateChannel = settings.Channel
	c.config.AutoUpdate = settings.AutoUpdate
	c.config.UpdateCheckInterval = settings.CheckInterval
	return c.saveUnsafe()
}

// GetUpdateChannel returns the update channel.
func (c *ConfigService) GetUpdateChannel() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.UpdateChannel
}

// SetUpdateChannel sets the update channel.
func (c *ConfigService) SetUpdateChannel(channel string) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.UpdateChannel = channel
	return c.saveUnsafe()
}

// IsAutoUpdateEnabled returns whether automatic updates are enabled.
func (c *ConfigService) IsAutoUpdateEnabled() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.AutoUpdate
}

// SetAutoUpdateEnabled enables or disables automatic updates.
func (c *ConfigService) SetAutoUpdateEnabled(enabled bool) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.AutoUpdate = enabled
	return c.saveUnsafe()
}

// GetUpdateCheckInterval returns the update check interval in hours.
func (c *ConfigService) GetUpdateCheckInterval() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.UpdateCheckInterval
}

// SetUpdateCheckInterval sets the update check interval in hours.
func (c *ConfigService) SetUpdateCheckInterval(hours int) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.UpdateCheckInterval = hours
	return c.saveUnsafe()
}

// GetLastUpdateCheck returns the last update check time.
func (c *ConfigService) GetLastUpdateCheck() time.Time {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.config.LastUpdateCheck
}

// SetLastUpdateCheck sets the last update check time.
func (c *ConfigService) SetLastUpdateCheck(t time.Time) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.config.LastUpdateCheck = t
	return c.saveUnsafe()
}

// ShouldCheckForUpdates returns true if it's time to check for updates.
func (c *ConfigService) ShouldCheckForUpdates() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if c.config.UpdateCheckInterval <= 0 {
		return false // Updates disabled
	}

	if c.config.LastUpdateCheck.IsZero() {
		return true // Never checked
	}

	interval := time.Duration(c.config.UpdateCheckInterval) * time.Hour
	return time.Since(c.config.LastUpdateCheck) >= interval
}
