package config

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

const (
	defaultActiveEnv = "prod"
	configDirName    = "clinic"
	configFileName   = "config.json"
	dirMode          = 0o700
	fileMode         = 0o600
)

var defaultURLs = map[string]string{
	"prod":    "https://clinic.pingcap.com",
	"staging": "https://staging-clinic.pingcap.com",
	"dev":     "https://dev-clinic.pingcap.com",
}

type Environment struct {
	URL    string `json:"url"`
	APIKey string `json:"api_key,omitempty"`
}

type ProfileRecord struct {
	Env                 string `json:"-"`
	ClusterID           string `json:"cluster_id"`
	ClusterName         string `json:"cluster_name,omitempty"`
	OrgID               string `json:"org_id,omitempty"`
	ParentID            string `json:"parent_id,omitempty"`
	ClusterDeployTypeV2 string `json:"cluster_deploy_type_v2,omitempty"`
	RangeStart          string `json:"range_start,omitempty"`
	RangeEnd            string `json:"range_end,omitempty"`
}

type ProfileDefaults = ProfileRecord

type CachedClusterRecord struct {
	ClusterID           string `json:"cluster_id"`
	Alias               string `json:"alias,omitempty"`
	OrgID               string `json:"org_id,omitempty"`
	ParentID            string `json:"parent_id,omitempty"`
	ClusterDeployTypeV2 string `json:"cluster_deploy_type_v2,omitempty"`
}

type EnvProfileContainer struct {
	ActiveProfile string                   `json:"active_profile,omitempty"`
	Profiles      map[string]ProfileRecord `json:"profiles"`
}

type Config struct {
	ActiveEnv    string                         `json:"active_env"`
	Environments map[string]Environment         `json:"environments"`
	EnvProfiles  map[string]EnvProfileContainer `json:"env_profiles"`

	ActiveProfile string                           `json:"-"`
	Profiles      map[string]ProfileDefaults       `json:"-"`
	ClusterCache  map[string][]CachedClusterRecord `json:"-"`
}

func ConfigPath() (string, error) {
	baseDir := os.Getenv("XDG_CONFIG_HOME")
	if baseDir == "" {
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return "", fmt.Errorf("resolve home directory: %w", err)
		}
		baseDir = filepath.Join(homeDir, ".config")
	}

	return filepath.Join(baseDir, configDirName, configFileName), nil
}

func LoadOrInit() (*Config, error) {
	path, err := ConfigPath()
	if err != nil {
		return nil, err
	}

	data, err := os.ReadFile(path)
	if err == nil {
		var cfg Config
		if err := json.Unmarshal(data, &cfg); err != nil {
			return nil, fmt.Errorf("decode config: %w", err)
		}
		if cfg.ensureDefaults() {
			if err := cfg.Save(); err != nil {
				return nil, err
			}
		}
		return &cfg, nil
	}
	if !errors.Is(err, os.ErrNotExist) {
		return nil, fmt.Errorf("read config: %w", err)
	}

	cfg := defaultConfig()
	if err := cfg.Save(); err != nil {
		return nil, err
	}
	return cfg, nil
}

func (c *Config) Save() error {
	path, err := ConfigPath()
	if err != nil {
		return err
	}

	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, dirMode); err != nil {
		return fmt.Errorf("create config dir: %w", err)
	}
	if err := os.Chmod(dir, dirMode); err != nil {
		return fmt.Errorf("chmod config dir: %w", err)
	}

	data, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return fmt.Errorf("encode config: %w", err)
	}
	data = append(data, '\n')

	file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fileMode)
	if err != nil {
		return fmt.Errorf("open config file: %w", err)
	}
	defer file.Close()

	if _, err := file.Write(data); err != nil {
		return fmt.Errorf("write config file: %w", err)
	}
	if err := file.Chmod(fileMode); err != nil {
		return fmt.Errorf("chmod config file: %w", err)
	}

	return nil
}

func (c *Config) ResolveEnv(override string) (Environment, error) {
	name := override
	if name == "" {
		name = c.ActiveEnv
	}

	env, ok := c.Environments[name]
	if !ok {
		return Environment{}, fmt.Errorf("unknown environment: %s", name)
	}

	return env, nil
}

func defaultConfig() *Config {
	environments := make(map[string]Environment, len(defaultURLs))
	envProfiles := make(map[string]EnvProfileContainer, len(defaultURLs))
	for name, url := range defaultURLs {
		environments[name] = Environment{URL: url}
		envProfiles[name] = EnvProfileContainer{
			Profiles: map[string]ProfileRecord{},
		}
	}

	cfg := &Config{
		ActiveEnv:    defaultActiveEnv,
		Environments: environments,
		EnvProfiles:  envProfiles,
	}
	cfg.ensureDefaults()
	return cfg
}

func (c *Config) ensureDefaults() bool {
	changed := false

	if c.Environments == nil {
		c.Environments = make(map[string]Environment, len(defaultURLs))
		changed = true
	}

	if c.EnvProfiles == nil {
		c.EnvProfiles = make(map[string]EnvProfileContainer, len(defaultURLs))
		changed = true
	}

	if c.ActiveEnv == "" {
		c.ActiveEnv = defaultActiveEnv
		changed = true
	}

	for name, url := range defaultURLs {
		if env, ok := c.Environments[name]; !ok {
			c.Environments[name] = Environment{URL: url}
			changed = true
		} else if env.URL == "" {
			env.URL = url
			c.Environments[name] = env
			changed = true
		}

		container, ok := c.EnvProfiles[name]
		if !ok {
			c.EnvProfiles[name] = EnvProfileContainer{
				Profiles: map[string]ProfileRecord{},
			}
			changed = true
			continue
		}
		if container.Profiles == nil {
			container.Profiles = map[string]ProfileRecord{}
			c.EnvProfiles[name] = container
			changed = true
		}
	}

	if _, ok := c.EnvProfiles[c.ActiveEnv]; !ok {
		c.EnvProfiles[c.ActiveEnv] = EnvProfileContainer{
			Profiles: map[string]ProfileRecord{},
		}
		changed = true
	}

	return changed
}
