package store

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"slices"
)

var builtinAliases = []*Alias{
	{Name: "mine", Command: "list --assignee $me", Description: "My assigned issues"},
	{Name: "due", Command: "list --status open", Description: "Open issues"},
	{Name: "inbox", Command: "list --mention $me", Description: "My mentions"},
}

func isBuiltinAlias(name string) bool {
	for _, a := range builtinAliases {
		if a.Name == name {
			return true
		}
	}
	return false
}

type Alias struct {
	Name        string `json:"name"`
	Command     string `json:"command"`
	Description string `json:"description,omitempty"`
}

type ServerConfig struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

type OIDCConfig struct {
	Issuer       string `json:"issuer"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
	PublicURL    string `json:"public_url"`
	UserClaim    string `json:"user_claim"`
}

type Config struct {
	path        string
	User        string       `json:"user"`
	Editor      string       `json:"editor"`
	UserAliases []*Alias     `json:"aliases"`
	Serve       ServerConfig `json:"serve"`
	Remote      ServerConfig `json:"remote"`
	OIDC        OIDCConfig   `json:"oidc"`
}

func FindDataRoot(std ...string) (string, error) {
	dir, err := filepath.Abs(".")
	if err != nil {
		return "", err
	}
	for {
		p := filepath.Join(dir, ".ax")
		if stat, err := os.Stat(p); err == nil {
			if stat.IsDir() {
				return p, nil
			}
		}
		if parent := filepath.Dir(dir); parent == dir {
			break
		} else {
			dir = parent
		}
	}
	home, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}
	stdpath := filepath.Join(std...)
	return filepath.Join(home, stdpath, "ax"), nil
}

func LoadConfigFile() (*Config, error) {
	configRoot, err := FindDataRoot(".config")
	if err != nil {
		return nil, err
	}
	path := filepath.Join(configRoot, "config.json")
	fc := &Config{path: path, UserAliases: []*Alias{}}
	data, err := os.ReadFile(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
	} else {
		if err := json.Unmarshal(data, fc); err != nil {
			return nil, err
		}
	}
	return fc, nil
}

func (c *Config) GetUser() string {
	if c.User != "" {
		return c.User
	}
	if u := os.Getenv("AX_USER"); u != "" {
		return u
	}
	if u, err := user.Current(); err == nil {
		return u.Username
	}
	return "unknown"
}

func (c *Config) GetEditor() string {
	if c.Editor != "" {
		return c.User
	}
	if u := os.Getenv("EDITOR"); u != "" {
		return u
	}
	return "vi"
}

func (c *Config) GetAlias(name string) (*Alias, error) {
	for _, a := range c.UserAliases {
		if a.Name == name {
			return a, nil
		}
	}
	return nil, errors.New("alias not found")
}

func (c *Config) SetAlias(alias *Alias) error {
	for i, a := range c.UserAliases {
		if a.Name == alias.Name {
			c.UserAliases[i] = alias
			return c.Save()
		}
	}
	c.UserAliases = append(c.UserAliases, alias)
	return c.Save()
}

func (c *Config) DeleteAlias(name string) error {
	if isBuiltinAlias(name) {
		return fmt.Errorf("cannot delete built-in alias %q", name)
	}
	for i, a := range c.UserAliases {
		if a.Name == name {
			c.UserAliases = slices.Delete(c.UserAliases, i, i+1)
			return c.Save()
		}
	}
	return errors.New("alias not found")
}

func (c *Config) ListAliases() ([]*Alias, error) {
	seen := make(map[string]bool)
	var result []*Alias
	for _, a := range builtinAliases {
		result = append(result, a)
		seen[a.Name] = true
	}
	for _, a := range c.UserAliases {
		if !seen[a.Name] {
			result = append(result, a)
			seen[a.Name] = true
		}
	}
	return result, nil
}

func (c *Config) GetOIDCConfig() (*OIDCConfig, bool) {
	if c.OIDC.Issuer == "" {
		return nil, false
	}
	cfg := c.OIDC
	if cfg.UserClaim == "" {
		cfg.UserClaim = "preferred_username"
	}
	return &cfg, true
}

func (c *Config) GetRemoteConfig() (*ServerConfig, bool) {
	if c.Remote.Host == "" {
		return nil, false
	}
	port := c.Remote.Port
	if port == 0 {
		port = 7000
	}
	return &ServerConfig{Host: c.Remote.Host, Port: port}, true
}

func (c *Config) GetServerConfig() *ServerConfig {
	host := c.Serve.Host
	if host == "" {
		host = "localhost"
	}
	port := c.Serve.Port
	if port == 0 {
		port = 7000
	}
	return &ServerConfig{Host: host, Port: port}
}

func (c *Config) Save() error {
	if err := os.MkdirAll(filepath.Dir(c.path), 0755); err != nil {
		return err
	}
	data, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(c.path, data, 0644)
}
