// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package config

import (
	"errors"
	"path/filepath"

	"github.com/derailed/k9s/internal/client"
	"github.com/derailed/k9s/internal/config/data"
)

// K9s tracks K9s configuration options.
type K9s struct {
	LiveViewAutoRefresh bool        `yaml:"liveViewAutoRefresh"`
	ScreenDumpDir       string      `yaml:"screenDumpDir,omitempty"`
	RefreshRate         int         `yaml:"refreshRate"`
	MaxConnRetry        int         `yaml:"maxConnRetry"`
	ReadOnly            bool        `yaml:"readOnly"`
	NoExitOnCtrlC       bool        `yaml:"noExitOnCtrlC"`
	UI                  UI          `yaml:"ui"`
	SkipLatestRevCheck  bool        `yaml:"skipLatestRevCheck"`
	DisablePodCounting  bool        `yaml:"disablePodCounting"`
	ShellPod            *ShellPod   `yaml:"shellPod"`
	ImageScans          *ImageScans `yaml:"imageScans"`
	Logger              *Logger     `yaml:"logger"`
	Thresholds          Threshold   `yaml:"thresholds"`
	manualRefreshRate   int
	manualHeadless      *bool
	manualLogoless      *bool
	manualCrumbsless    *bool
	manualReadOnly      *bool
	manualCommand       *string
	manualScreenDumpDir *string
	dir                 *data.Dir
	activeContextName   string
	activeConfig        *data.Config
	conn                client.Connection
	ks                  data.KubeSettings
}

// NewK9s create a new K9s configuration.
func NewK9s(conn client.Connection, ks data.KubeSettings) *K9s {
	return &K9s{
		RefreshRate:   defaultRefreshRate,
		MaxConnRetry:  defaultMaxConnRetry,
		ScreenDumpDir: AppDumpsDir,
		Logger:        NewLogger(),
		Thresholds:    NewThreshold(),
		ShellPod:      NewShellPod(),
		ImageScans:    NewImageScans(),
		dir:           data.NewDir(AppContextsDir),
		conn:          conn,
		ks:            ks,
	}
}

func (k *K9s) resetConnection(conn client.Connection) {
	k.conn = conn
}

// Save saves the k9s config to dis.
func (k *K9s) Save() error {
	if k.activeConfig != nil {
		path := filepath.Join(
			AppContextsDir,
			data.SanitizeContextSubpath(k.activeConfig.Context.ClusterName, k.activeContextName),
			data.MainConfigFile,
		)
		return k.activeConfig.Save(path)
	}

	return nil
}

// Refine merges k9s configs.
func (k *K9s) Refine(k1 *K9s) {
	k.LiveViewAutoRefresh = k1.LiveViewAutoRefresh
	k.ScreenDumpDir = k1.ScreenDumpDir
	k.RefreshRate = k1.RefreshRate
	k.MaxConnRetry = k1.MaxConnRetry
	k.ReadOnly = k1.ReadOnly
	k.NoExitOnCtrlC = k1.NoExitOnCtrlC
	k.UI = k1.UI
	k.SkipLatestRevCheck = k1.SkipLatestRevCheck
	k.DisablePodCounting = k1.DisablePodCounting
	k.ShellPod = k1.ShellPod
	k.ImageScans = k1.ImageScans
	k.Logger = k1.Logger
	k.Thresholds = k1.Thresholds
}

// Override overrides k9s config from cli args.
func (k *K9s) Override(k9sFlags *Flags) {
	if *k9sFlags.RefreshRate != DefaultRefreshRate {
		k.OverrideRefreshRate(*k9sFlags.RefreshRate)
	}

	k.OverrideHeadless(*k9sFlags.Headless)
	k.OverrideLogoless(*k9sFlags.Logoless)
	k.OverrideCrumbsless(*k9sFlags.Crumbsless)
	k.OverrideReadOnly(*k9sFlags.ReadOnly)
	k.OverrideWrite(*k9sFlags.Write)
	k.OverrideCommand(*k9sFlags.Command)
	k.OverrideScreenDumpDir(*k9sFlags.ScreenDumpDir)
}

// OverrideScreenDumpDir set the screen dump dir manually.
func (k *K9s) OverrideScreenDumpDir(dir string) {
	k.manualScreenDumpDir = &dir
}

// GetScreenDumpDir fetch screen dumps dir.
func (k *K9s) GetScreenDumpDir() string {
	screenDumpDir := k.ScreenDumpDir
	if k.manualScreenDumpDir != nil && *k.manualScreenDumpDir != "" {
		screenDumpDir = *k.manualScreenDumpDir
	}
	if screenDumpDir == "" {
		screenDumpDir = AppDumpsDir
	}

	return screenDumpDir
}

// Reset resets configuration and context.
func (k *K9s) Reset() {
	k.activeConfig, k.activeContextName = nil, ""
}

// ActiveScreenDumpsDir fetch context specific screen dumps dir.
func (k *K9s) ActiveScreenDumpsDir() string {
	return filepath.Join(k.GetScreenDumpDir(), k.ActiveContextDir())
}

// ActiveContextDir fetch current cluster/context path.
func (k *K9s) ActiveContextDir() string {
	if k.activeConfig == nil {
		return "na"
	}

	return data.SanitizeContextSubpath(
		k.activeConfig.Context.ClusterName,
		k.ActiveContextName(),
	)
}

// ActiveContextNamespace fetch the context active ns.
func (k *K9s) ActiveContextNamespace() (string, error) {
	if k.activeConfig != nil {
		return k.activeConfig.Context.Namespace.Active, nil
	}

	return "", errors.New("context config is not set")
}

// ActiveContextName returns the active context name.
func (k *K9s) ActiveContextName() string {
	return k.activeContextName
}

// ActiveContext returns the currently active context.
func (k *K9s) ActiveContext() (*data.Context, error) {
	if k.activeConfig != nil {
		return k.activeConfig.Context, nil
	}

	ct, err := k.ActivateContext(k.activeContextName)
	if err != nil {
		return nil, err
	}

	return ct, nil
}

// ActivateContext initializes the active context is not present.
func (k *K9s) ActivateContext(n string) (*data.Context, error) {
	k.activeContextName = n
	ct, err := k.ks.GetContext(k.activeContextName)
	if err != nil {
		return nil, err
	}
	k.activeConfig, err = k.dir.Load(n, ct)
	if err != nil {
		return nil, err
	}
	// If the context specifies a default namespace, use it!
	if k.conn != nil {
		if ns := k.conn.ActiveNamespace(); ns != client.BlankNamespace {
			k.activeConfig.Context.Namespace.Active = ns
		} else {
			k.activeConfig.Context.Namespace.Active = client.DefaultNamespace
		}
	}

	return k.activeConfig.Context, nil
}

// OverrideRefreshRate set the refresh rate manually.
func (k *K9s) OverrideRefreshRate(r int) {
	k.manualRefreshRate = r
}

// OverrideHeadless toggle the header manually.
func (k *K9s) OverrideHeadless(b bool) {
	k.manualHeadless = &b
}

// OverrideLogoless toggle the k9s logo manually.
func (k *K9s) OverrideLogoless(b bool) {
	k.manualLogoless = &b
}

// OverrideCrumbsless tooh the crumbslessness manually.
func (k *K9s) OverrideCrumbsless(b bool) {
	k.manualCrumbsless = &b
}

// OverrideReadOnly set the readonly mode manually.
func (k *K9s) OverrideReadOnly(b bool) {
	if b {
		k.manualReadOnly = &b
	}
}

// OverrideWrite set the write mode manually.
func (k *K9s) OverrideWrite(b bool) {
	if b {
		var flag bool
		k.manualReadOnly = &flag
	}
}

// OverrideCommand set the command manually.
func (k *K9s) OverrideCommand(cmd string) {
	k.manualCommand = &cmd
}

// IsHeadless returns headless setting.
func (k *K9s) IsHeadless() bool {
	h := k.UI.Headless
	if k.manualHeadless != nil && *k.manualHeadless {
		h = *k.manualHeadless
	}

	return h
}

// IsLogoless returns logoless setting.
func (k *K9s) IsLogoless() bool {
	h := k.UI.Logoless
	if k.manualLogoless != nil && *k.manualLogoless {
		h = *k.manualLogoless
	}

	return h
}

// IsCrumbsless returns crumbsless setting.
func (k *K9s) IsCrumbsless() bool {
	h := k.UI.Crumbsless
	if k.manualCrumbsless != nil && *k.manualCrumbsless {
		h = *k.manualCrumbsless
	}

	return h
}

// GetRefreshRate returns the current refresh rate.
func (k *K9s) GetRefreshRate() int {
	rate := k.RefreshRate
	if k.manualRefreshRate != 0 {
		rate = k.manualRefreshRate
	}

	return rate
}

// IsReadOnly returns the readonly setting.
func (k *K9s) IsReadOnly() bool {
	readOnly := k.ReadOnly
	if k.manualReadOnly != nil {
		readOnly = *k.manualReadOnly
	}
	if k.activeConfig != nil && k.activeConfig.Context.ReadOnly {
		readOnly = true
	}

	return readOnly
}

func (k *K9s) validateDefaults() {
	if k.RefreshRate <= 0 {
		k.RefreshRate = defaultRefreshRate
	}
	if k.MaxConnRetry <= 0 {
		k.MaxConnRetry = defaultMaxConnRetry
	}
}

// Validate the current configuration.
func (k *K9s) Validate(c client.Connection, ks data.KubeSettings) {
	k.validateDefaults()
	if k.activeConfig == nil {
		if n, err := ks.CurrentContextName(); err == nil {
			_, _ = k.ActivateContext(n)
		}
	}
	if k.ImageScans == nil {
		k.ImageScans = NewImageScans()
	}
	if k.ShellPod == nil {
		k.ShellPod = NewShellPod()
	}
	k.ShellPod.Validate(c, ks)

	if k.Logger == nil {
		k.Logger = NewLogger()
	} else {
		k.Logger.Validate(c, ks)
	}
	if k.Thresholds == nil {
		k.Thresholds = NewThreshold()
	}
	k.Thresholds.Validate(c, ks)

	if k.activeConfig != nil {
		k.activeConfig.Validate(c, ks)
	}
}
