package config

import (
	"encoding/json"
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"regexp"
	"time"
)

type Config struct {
	Version              int            `json:"version"`
	LogLevel             string         `json:"log_level"`
	DefaultPage          string         `json:"default_page"`
	Font                 string         `json:"font,omitempty"`
	ShowLabelBackground  bool           `json:"show_label_background"`
	Devices              []DeviceConfig `json:"devices"`
	Pages                []PageConfig   `json:"pages"`
	AutoSwitch           []SwitchRule   `json:"auto_switch"`
	Screensaver          ScreensaverCfg `json:"screensaver"`
	Timing               TimingConfig   `json:"timing,omitempty"`
}

type TimingConfig struct {
	LongPressMs int `json:"long_press_ms,omitempty"`
	DoubleTapMs int `json:"double_tap_ms,omitempty"`
}

const (
	DefaultLongPressMs = 500
	DefaultDoubleTapMs = 300
)

type DeviceConfig struct {
	Serial     string `json:"serial"`
	Brightness int    `json:"brightness"`
	Rotation   int    `json:"rotation,omitempty"`
}

type PageConfig struct {
	Name        string          `json:"name"`
	Icon        string          `json:"icon,omitempty"`
	Keys        []KeyConfig     `json:"keys"`
	DynamicKeys *DynamicKeyGen  `json:"dynamic_keys,omitempty"`
	Background  string          `json:"background,omitempty"`
	Screensaver *ScreensaverCfg `json:"screensaver,omitempty"`
}

type DynamicKeyGen struct {
	Command  string `json:"command,omitempty"`
	Script   string `json:"script,omitempty"`
	Interval string `json:"interval,omitempty"`
	Timeout  string `json:"timeout,omitempty"`
	MaxKeys  int    `json:"max_keys,omitempty"`
}

type KeyConfig struct {
	Index      int           `json:"index"`
	Icon       string        `json:"icon,omitempty"`
	Background string        `json:"background,omitempty"`
	Label      string        `json:"label,omitempty"`
	Font       string        `json:"font,omitempty"`
	FontSize   *float64      `json:"font_size,omitempty"`
	IconScale  *float64      `json:"icon_scale,omitempty"`
	Actions    []KeyAction   `json:"actions,omitempty"`
	Display    *DisplayCfg   `json:"display,omitempty"`
}

type KeyAction struct {
	Trigger string `json:"trigger"`
	Action  `json:",inline"`
}

type Action struct {
	Type       string `json:"type"`
	Command    string `json:"command,omitempty"`
	Builtin    string `json:"builtin,omitempty"`
	Script     string `json:"script,omitempty"`
	Page       string `json:"page,omitempty"`
	Background bool   `json:"background,omitempty"`
	Keys       string `json:"keys,omitempty"`
}

func (k *KeyConfig) UnmarshalJSON(data []byte) error {
	type Alias KeyConfig
	aux := &struct {
		OldAction *Action `json:"action"`
		*Alias
	}{Alias: (*Alias)(k)}
	if err := json.Unmarshal(data, aux); err != nil {
		return err
	}
	if aux.OldAction != nil && len(k.Actions) == 0 {
		k.Actions = []KeyAction{{Trigger: "tap", Action: *aux.OldAction}}
	}
	return nil
}

type DisplayCfg struct {
	Command  string `json:"command,omitempty"`
	Script   string `json:"script,omitempty"`
	Interval string `json:"interval"`
	MaxLen   int    `json:"max_len,omitempty"`
	Timeout  string `json:"timeout,omitempty"`
}

type SwitchRule struct {
	WMClass string   `json:"wm_class"`
	Title   string   `json:"title,omitempty"`
	Page    string   `json:"page"`
	Devices []string `json:"devices,omitempty"`
	Stay    bool     `json:"stay,omitempty"`
}

type ScreensaverCfg struct {
	Enabled     bool   `json:"enabled"`
	IdleSeconds int    `json:"idle_seconds,omitempty"`
	Image       string `json:"image,omitempty"`
	Brightness  int    `json:"brightness,omitempty"`
}

func DefaultConfig() *Config {
	return &Config{
		Version:             1,
		LogLevel:            "info",
		DefaultPage:         "default",
		ShowLabelBackground: true,
		Pages: []PageConfig{
			{
				Name: "default",
				Keys: []KeyConfig{},
			},
		},
		Timing: TimingConfig{
			LongPressMs: DefaultLongPressMs,
			DoubleTapMs: DefaultDoubleTapMs,
		},
	}
}

func ConfigPath(path string) string {
	if path != "" {
		return path
	}
	home, err := os.UserHomeDir()
	if err != nil {
		return "config.json"
	}
	return filepath.Join(home, ".config", "streamdeck-lets-go", "config.json")
}

func LoadConfig(path string) (*Config, error) {
	path = ConfigPath(path)
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			slog.Info("config not found, using defaults", "path", path)
			return DefaultConfig(), nil
		}
		return nil, fmt.Errorf("reading config: %w", err)
	}

	var cfg Config
	if err := json.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parsing config: %w", err)
	}

	if cfg.Timing.LongPressMs <= 0 {
		cfg.Timing.LongPressMs = DefaultLongPressMs
	}
	if cfg.Timing.DoubleTapMs <= 0 {
		cfg.Timing.DoubleTapMs = DefaultDoubleTapMs
	}

	if err := cfg.Validate(); err != nil {
		return nil, fmt.Errorf("invalid config: %w", err)
	}

	return &cfg, nil
}

func (c *Config) Save(path string) error {
	path = ConfigPath(path)
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return fmt.Errorf("creating config dir: %w", err)
	}
	data, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return fmt.Errorf("marshaling config: %w", err)
	}
	if err := os.WriteFile(path, data, 0644); err != nil {
		return fmt.Errorf("writing config: %w", err)
	}
	return nil
}

func validTrigger(t string) bool {
	switch t {
	case "tap", "long_press", "double_tap", "hold_start", "hold_end":
		return true
	}
	return false
}

func (c *Config) Validate() error {
	if c.Version < 1 {
		return fmt.Errorf("unsupported config version: %d", c.Version)
	}
	if c.DefaultPage == "" {
		return fmt.Errorf("default_page is required")
	}
	pageNames := make(map[string]bool)
	for _, p := range c.Pages {
		if p.Name == "" {
			return fmt.Errorf("page name is required")
		}
		if pageNames[p.Name] {
			return fmt.Errorf("duplicate page name: %s", p.Name)
		}
		pageNames[p.Name] = true

		seenKeys := make(map[int]bool)
		for _, k := range p.Keys {
			if k.Index < 0 {
				return fmt.Errorf("page %s: key index must be >= 0", p.Name)
			}
			if seenKeys[k.Index] {
				return fmt.Errorf("page %s: duplicate key index %d", p.Name, k.Index)
			}
			seenKeys[k.Index] = true

			seenTriggers := make(map[string]bool)
			for _, ka := range k.Actions {
				if ka.Trigger == "" {
					return fmt.Errorf("page %s key %d: action trigger is required", p.Name, k.Index)
				}
				if !validTrigger(ka.Trigger) {
					return fmt.Errorf("page %s key %d: unknown trigger %q", p.Name, k.Index, ka.Trigger)
				}
				if seenTriggers[ka.Trigger] {
					return fmt.Errorf("page %s key %d: duplicate trigger %q", p.Name, k.Index, ka.Trigger)
				}
				seenTriggers[ka.Trigger] = true

				switch ka.Type {
				case "command":
					if ka.Command == "" {
						return fmt.Errorf("page %s key %d: command required for command action", p.Name, k.Index)
					}
				case "builtin":
				case "script":
					if ka.Script == "" {
						return fmt.Errorf("page %s key %d: script path required for script action", p.Name, k.Index)
					}
				case "page":
					if ka.Page == "" {
						return fmt.Errorf("page %s key %d: page name required for page action", p.Name, k.Index)
					}
				case "keyboard":
					if ka.Keys == "" {
						return fmt.Errorf("page %s key %d: keys required for keyboard action", p.Name, k.Index)
					}
				case "":
					return fmt.Errorf("page %s key %d: action type is required", p.Name, k.Index)
				default:
					return fmt.Errorf("page %s key %d: unknown action type: %s", p.Name, k.Index, ka.Type)
				}
			}

			if k.Display != nil {
				if k.Display.Command == "" && k.Display.Script == "" {
					return fmt.Errorf("page %s key %d: display requires command or script", p.Name, k.Index)
				}
				if k.Display.Command != "" && k.Display.Script != "" {
					return fmt.Errorf("page %s key %d: display command and script are mutually exclusive", p.Name, k.Index)
				}
				if k.Display.Interval == "" {
					return fmt.Errorf("page %s key %d: display interval is required", p.Name, k.Index)
				}
				interval, err := time.ParseDuration(k.Display.Interval)
				if err != nil {
					return fmt.Errorf("page %s key %d: invalid display interval %q: %w", p.Name, k.Index, k.Display.Interval, err)
				}
				if interval < time.Second {
					return fmt.Errorf("page %s key %d: display interval must be at least 1s", p.Name, k.Index)
				}
				if interval > 24*time.Hour {
					return fmt.Errorf("page %s key %d: display interval must not exceed 24h", p.Name, k.Index)
				}
				if k.Display.Timeout != "" {
					if _, err := time.ParseDuration(k.Display.Timeout); err != nil {
						return fmt.Errorf("page %s key %d: invalid display timeout %q: %w", p.Name, k.Index, k.Display.Timeout, err)
					}
				}
				if k.Display.MaxLen < 0 {
					return fmt.Errorf("page %s key %d: max_len must be >= 0", p.Name, k.Index)
				}
				if k.Display.MaxLen > 4096 {
					return fmt.Errorf("page %s key %d: max_len must be <= 4096", p.Name, k.Index)
				}
			}
		}

		if p.DynamicKeys != nil {
			if p.DynamicKeys.Command == "" && p.DynamicKeys.Script == "" {
				return fmt.Errorf("page %s: dynamic_keys requires command or script", p.Name)
			}
			if p.DynamicKeys.Command != "" && p.DynamicKeys.Script != "" {
				return fmt.Errorf("page %s: dynamic_keys command and script are mutually exclusive", p.Name)
			}
			if p.DynamicKeys.Interval != "" {
				interval, err := time.ParseDuration(p.DynamicKeys.Interval)
				if err != nil {
					return fmt.Errorf("page %s: invalid dynamic_keys interval %q: %w", p.Name, p.DynamicKeys.Interval, err)
				}
				if interval < time.Second {
					return fmt.Errorf("page %s: dynamic_keys interval must be at least 1s", p.Name)
				}
				if interval > 24*time.Hour {
					return fmt.Errorf("page %s: dynamic_keys interval must not exceed 24h", p.Name)
				}
			}
			if p.DynamicKeys.Timeout != "" {
				if _, err := time.ParseDuration(p.DynamicKeys.Timeout); err != nil {
					return fmt.Errorf("page %s: invalid dynamic_keys timeout %q: %w", p.Name, p.DynamicKeys.Timeout, err)
				}
			}
			if p.DynamicKeys.MaxKeys < 0 {
				return fmt.Errorf("page %s: dynamic_keys max_keys must be >= 0", p.Name)
			}
		}
	}

	for _, p := range c.Pages {
		for _, k := range p.Keys {
			for _, ka := range k.Actions {
				if ka.Type == "page" {
					if !pageNames[ka.Page] {
						return fmt.Errorf("page %s key %d: references unknown page %q", p.Name, k.Index, ka.Page)
					}
				}
			}
		}
	}

	for _, sr := range c.AutoSwitch {
		if sr.WMClass == "" && sr.Title == "" {
			return fmt.Errorf("auto_switch rule: wm_class or title is required")
		}
		if !pageNames[sr.Page] {
			return fmt.Errorf("auto_switch rule: references unknown page %q", sr.Page)
		}
		if _, err := regexp.Compile(sr.WMClass); sr.WMClass != "" && err != nil {
			return fmt.Errorf("auto_switch rule: invalid wm_class regex %q: %w", sr.WMClass, err)
		}
		if _, err := regexp.Compile(sr.Title); sr.Title != "" && err != nil {
			return fmt.Errorf("auto_switch rule: invalid title regex %q: %w", sr.Title, err)
		}
	}

	if !pageNames[c.DefaultPage] {
		return fmt.Errorf("default_page %q not found in pages", c.DefaultPage)
	}
	return nil
}
