// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

// Package config handles loading and saving lunatask-mcp-server configuration.
package config

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/BurntSushi/toml"
)

// ErrNotFound indicates the config file doesn't exist.
var ErrNotFound = errors.New("config file not found")

// Config represents the lunatask-mcp-server configuration file structure.
type Config struct {
	Server   ServerConfig `toml:"server"`
	Tools    ToolsConfig  `toml:"tools"`
	Timezone string       `toml:"timezone"`
	Areas    []Area       `toml:"areas"`
	Habits   []Habit      `toml:"habits"`
}

// ServerConfig holds server-related settings.
type ServerConfig struct {
	Host      string `toml:"host"`
	Port      int    `toml:"port"`
	Transport string `toml:"transport"`
}

// ToolsConfig controls which MCP tools are enabled.
type ToolsConfig struct {
	GetTimestamp       bool `toml:"get_timestamp"`
	CreateTask         bool `toml:"create_task"`
	UpdateTask         bool `toml:"update_task"`
	DeleteTask         bool `toml:"delete_task"`
	TrackHabitActivity bool `toml:"track_habit_activity"`
}

// Area represents a Lunatask area of life with its goals.
type Area struct {
	ID    string `json:"id"    toml:"id"`
	Name  string `json:"name"  toml:"name"`
	Key   string `json:"key"   toml:"key"`
	Goals []Goal `json:"goals" toml:"goals"`
}

// Goal represents a goal within an area.
type Goal struct {
	ID   string `json:"id"   toml:"id"`
	Name string `json:"name" toml:"name"`
	Key  string `json:"key"  toml:"key"`
}

// Habit represents a trackable habit.
type Habit struct {
	ID   string `json:"id"   toml:"id"`
	Name string `json:"name" toml:"name"`
	Key  string `json:"key"  toml:"key"`
}

// Defaults holds default selections.
type Defaults struct {
	Area string `toml:"area"`
}

// Path returns the path to the config file.
func Path() (string, error) {
	configDir, err := os.UserConfigDir()
	if err != nil {
		return "", fmt.Errorf("getting config dir: %w", err)
	}

	return filepath.Join(configDir, "lunatask-mcp-server", "config.toml"), nil
}

// Load reads the config file. Returns ErrNotFound if the file doesn't exist.
func Load() (*Config, error) {
	path, err := Path()
	if err != nil {
		return nil, err
	}

	return LoadFrom(path)
}

// LoadFrom reads the config from the specified path.
func LoadFrom(path string) (*Config, error) {
	var cfg Config
	if _, err := toml.DecodeFile(path, &cfg); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, ErrNotFound
		}

		return nil, fmt.Errorf("decoding config: %w", err)
	}

	cfg.applyDefaults()

	return &cfg, nil
}

// Save writes the config to the default path.
func (c *Config) Save() error {
	path, err := Path()
	if err != nil {
		return err
	}

	return c.SaveTo(path)
}

// SaveTo writes the config to the specified path.
func (c *Config) SaveTo(path string) error {
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return fmt.Errorf("creating config dir: %w", err)
	}

	//nolint:gosec,mnd // path is from user config dir; 0o600 is intentional
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
	if err != nil {
		return fmt.Errorf("creating config file: %w", err)
	}

	if err := toml.NewEncoder(f).Encode(c); err != nil {
		_ = f.Close()

		return fmt.Errorf("encoding config: %w", err)
	}

	if err := f.Close(); err != nil {
		return fmt.Errorf("closing config file: %w", err)
	}

	return nil
}

// AreaByKey finds an area by its key.
func (c *Config) AreaByKey(key string) *Area {
	for i := range c.Areas {
		if c.Areas[i].Key == key {
			return &c.Areas[i]
		}
	}

	return nil
}

// AreaByID finds an area by its ID.
func (c *Config) AreaByID(id string) *Area {
	for i := range c.Areas {
		if c.Areas[i].ID == id {
			return &c.Areas[i]
		}
	}

	return nil
}

// HabitByKey finds a habit by its key.
func (c *Config) HabitByKey(key string) *Habit {
	for i := range c.Habits {
		if c.Habits[i].Key == key {
			return &c.Habits[i]
		}
	}

	return nil
}

// HabitByID finds a habit by its ID.
func (c *Config) HabitByID(id string) *Habit {
	for i := range c.Habits {
		if c.Habits[i].ID == id {
			return &c.Habits[i]
		}
	}

	return nil
}

// GoalByKey finds a goal within this area by its key.
func (a *Area) GoalByKey(key string) *Goal {
	for i := range a.Goals {
		if a.Goals[i].Key == key {
			return &a.Goals[i]
		}
	}

	return nil
}

// GoalByID finds a goal within this area by its ID.
func (a *Area) GoalByID(id string) *Goal {
	for i := range a.Goals {
		if a.Goals[i].ID == id {
			return &a.Goals[i]
		}
	}

	return nil
}

// GoalMatch pairs a goal with its parent area.
type GoalMatch struct {
	Goal *Goal
	Area *Area
}

// FindGoalsByKey returns all goals matching the given key across all areas.
func (c *Config) FindGoalsByKey(key string) []GoalMatch {
	var matches []GoalMatch

	for i := range c.Areas {
		area := &c.Areas[i]

		for j := range area.Goals {
			if area.Goals[j].Key == key {
				matches = append(matches, GoalMatch{
					Goal: &area.Goals[j],
					Area: area,
				})
			}
		}
	}

	return matches
}

// GoalByID finds a goal by its ID across all areas.
func (c *Config) GoalByID(id string) *GoalMatch {
	for i := range c.Areas {
		area := &c.Areas[i]

		for j := range area.Goals {
			if area.Goals[j].ID == id {
				return &GoalMatch{
					Goal: &area.Goals[j],
					Area: area,
				}
			}
		}
	}

	return nil
}

// applyDefaults sets default values for unset fields.
func (c *Config) applyDefaults() {
	if c.Server.Host == "" {
		c.Server.Host = "localhost"
	}

	if c.Server.Port == 0 {
		c.Server.Port = 8080
	}

	if c.Server.Transport == "" {
		c.Server.Transport = "stdio"
	}

	if c.Timezone == "" {
		c.Timezone = "UTC"
	}

	c.Tools.ApplyDefaults()
}

// ApplyDefaults enables all tools by default.
func (t *ToolsConfig) ApplyDefaults() {
	// Use a marker to detect if tools section was present in config.
	// If all are false, apply defaults (all true).
	if !t.GetTimestamp && !t.CreateTask && !t.UpdateTask && !t.DeleteTask && !t.TrackHabitActivity {
		t.GetTimestamp = true
		t.CreateTask = true
		t.UpdateTask = true
		t.DeleteTask = true
		t.TrackHabitActivity = true
	}
}
