//  Copyright 2023 Google Inc. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

// Package cfg is package responsible to loading and accessing the guest environment configuration.
package cfg

import (
	"fmt"
	"runtime"

	"gopkg.in/ini.v1"
)

var (
	// instance is the single instance of configuration sections, once loaded this package
	// should always return it.
	instance *Sections

	// configFile is a pointer to a function which takes the current OS name and returns
	// an appropriate config file name. Replaceable by unit tests.
	configFile = defaultConfigFile

	// dataSource is a pointer to a data source loading/defining function, unit tests will
	// want to change this pointer to whatever makes sense to its implementation.
	dataSources = defaultDataSources
)

const (
	winConfigPath  = `C:\Program Files\Google\Compute Engine\instance_configs.cfg`
	unixConfigPath = `/etc/default/instance_configs.cfg`

	defaultConfig = `
[Core]
cloud_logging_enabled = true

[Accounts]
deprovision_remove = false
gpasswd_add_cmd = gpasswd -a {user} {group}
gpasswd_remove_cmd = gpasswd -d {user} {group}
groupadd_cmd = groupadd {group}
groups = adm,dip,docker,lxd,plugdev,video
reuse_homedir = false
useradd_cmd = useradd -m -s /bin/bash -p * {user}
userdel_cmd = userdel -r {user}

[Daemons]
accounts_daemon = true
clock_skew_daemon = true
network_daemon = true

[IpForwarding]
ethernet_proto_id = 66
ip_aliases = true
target_instance_ips = true

[Instance]
instance_id =
instance_id_dir = /etc/google_instance_id

[InstanceSetup]
host_key_dir = /etc/ssh
host_key_types = ecdsa,ed25519,rsa
network_enabled = true
optimize_local_ssd = true
set_boto_config = true
set_host_keys = true
set_multiqueue = true

[MetadataScripts]
default_shell = /bin/bash
run_dir =
shutdown = true
shutdown-windows = true
startup = true
startup-windows = true
sysprep-specialize = true

[NetworkInterfaces]
dhcp_command =
ip_forwarding = true
setup = true
manage_primary_nic =
restore_debian12_netplan_config = true
vlan_setup_enabled = false

[OSLogin]
cert_authentication = true

[MDS]
disable-https-mds-setup = true
enable-https-mds-native-cert-store = false

[Routes]
enable_monitor = false
monitor_interval = 10s

[Snapshots]
enabled = false
snapshot_service_ip = 169.254.169.254
snapshot_service_port = 8081
timeout_in_seconds = 60

[Unstable]
command_monitor_enabled = false
command_pipe_mode = 0770
command_pipe_group =
command_request_timeout = 10s
systemd_config_dir = /usr/lib/systemd/network
`

	defaultConfigFreeBSD = `
[Core]
cloud_logging_enabled = true

[Accounts]
deprovision_remove = false
gpasswd_add_cmd = pw groupmod {group} -m {user}
gpasswd_remove_cmd = pw groupmod {group} -d {user}
groupadd_cmd = pw groupadd {group}
groups = adm,dip,docker,lxd,plugdev,video
reuse_homedir = false
useradd_cmd = pw useradd {user} -m -s /bin/sh -h -
userdel_cmd = pw userdel {user} -r

[Daemons]
accounts_daemon = true
clock_skew_daemon = true
network_daemon = true

[IpForwarding]
ethernet_proto_id = 66
ip_aliases = true
target_instance_ips = true

[Instance]
instance_id =
instance_id_dir = /etc/google_instance_id

[InstanceSetup]
host_key_dir = /etc/ssh
host_key_types = ecdsa,ed25519,rsa
network_enabled = true
optimize_local_ssd = true
set_boto_config = true
set_host_keys = true
set_multiqueue = true

[MetadataScripts]
default_shell = /bin/bash
run_dir =
shutdown = true
shutdown-windows = true
startup = true
startup-windows = true
sysprep-specialize = true

[NetworkInterfaces]
dhcp_command =
ip_forwarding = true
setup = true
manage_primary_nic =
restore_debian12_netplan_config = true
vlan_setup_enabled = false

[OSLogin]
cert_authentication = true

[MDS]
disable-https-mds-setup = true
enable-https-mds-native-cert-store = false

[Routes]
enable_monitor = false
monitor_interval = 10s

[Snapshots]
enabled = false
snapshot_service_ip = 169.254.169.254
snapshot_service_port = 8081
timeout_in_seconds = 60

[Unstable]
command_monitor_enabled = false
command_pipe_mode = 0770
command_pipe_group =
command_request_timeout = 10s
systemd_config_dir = /usr/lib/systemd/network
`
)

// Core contains the core configuration entries of guest agent, all
// configurations not tied/specific to a subsystem are defined in here.
type Core struct {
	// CloudLoggingEnabled config toggle controls Guest Agent cloud logger.
	// Disabling it will stop Guest Agent for configuring and logging to Cloud Logging.
	CloudLoggingEnabled bool `ini:"cloud_logging_enabled,omitempty"`
}

// Sections encapsulates all the configuration sections.
type Sections struct {
	// Core defines the core guest-agent's configuration entries/keys.
	Core *Core `ini:"Core,omitempty"`
	// AccountManager defines the address management configurations. It takes precedence over instance's
	// and project's metadata configuration. The default configuration doesn't define values to it, if the
	// user has defined it then we shouldn't even consider metadata values. Users must check if this
	// pointer is nil or not.
	AccountManager *AccountManager `ini:"accountManager,omitempty"`

	// Accounts defines the non windows account management options, behaviors and commands.
	Accounts *Accounts `ini:"Accounts,omitempty"`

	// AddressManager defines the address management configurations. It takes precedence over instance's
	// and project's metadata configuration. The default configuration doesn't define values to it, if the
	// user has defined it then we shouldn't even consider metadata values. Users must check if this
	// pointer is nil or not.
	AddressManager *AddressManager `ini:"addressManager,omitempty"`

	// Daemons defines the availability of clock skew, network and account managers.
	Daemons *Daemons `ini:"Daemons,omitempty"`

	// Diagnostics defines the diagnostics configurations. It takes precedence over instance's
	// and project's metadata configuration. The default configuration doesn't define values to it, if the
	// user has defined it then we shouldn't even consider metadata values. Users must check if this
	// pointer is nil or not.
	Diagnostics *Diagnostics `ini:"diagnostics,omitempty"`

	// IPForwarding defines the ip forwarding configuration options.
	IPForwarding *IPForwarding `ini:"IpForwarding,omitempty"`

	// Instance defines the instance ID handling behaviors, i.e. where to read the ID from etc.
	Instance *Instance `ini:"Instance,omitempty"`

	// InstanceSetup defines options to basic instance setup options i.e. optimize local ssd, network,
	// host keys etc.
	InstanceSetup *InstanceSetup `ini:"InstanceSetup,omitempty"`

	// MetadataScripts contains the configurations of the metadata-scripts service.
	MetadataScripts *MetadataScripts `ini:"MetadataScripts,omitempty"`

	// NetworkInterfaces defines if the network interfaces should be managed/configured by guest-agent
	// as well as the commands definitions for network configuration.
	NetworkInterfaces *NetworkInterfaces `ini:"NetworkInterfaces,omitempty"`

	// OSLogin defines the OS Login configuration options.
	OSLogin *OSLogin `ini:"OSLogin,omitempty"`

	// MDS defines the MDS configuration options.
	MDS *MDS `ini:"MDS,omitempty"`

	// Routes defines configuration options specific to the routes monitor.
	Routes *Routes `ini:"Routes,omitempty"`

	// Snpashots defines the snapshot listener configuration and behavior i.e. the server address and port.
	Snapshots *Snapshots `ini:"Snapshots,omitempty"`

	// Unstable is a "under development feature flags" section. No stability or long term support is
	// guaranteed for any keys under this section. No application, script or utility should rely on it.
	Unstable *Unstable `ini:"Unstable,omitempty"`

	// WSFC defines the wsfc configurations. It takes precedence over instance's and project's
	// metadata configuration. The default configuration doesn't define values to it, if the user
	// has defined it then we shouldn't even consider metadata values. Users must check if this
	// pointer is nil or not.
	WSFC *WSFC `ini:"wsfc,omitempty"`
}

// AccountManager contains the configurations of AccountManager section.
type AccountManager struct {
	Disable bool `ini:"disable,omitempty"`
}

// Accounts contains the configurations of Accounts section.
type Accounts struct {
	DeprovisionRemove bool   `ini:"deprovision_remove,omitempty"`
	GPasswdAddCmd     string `ini:"gpasswd_add_cmd,omitempty"`
	GPasswdRemoveCmd  string `ini:"gpasswd_remove_cmd,omitempty"`
	GroupAddCmd       string `ini:"groupadd_cmd,omitempty"`
	Groups            string `ini:"groups,omitempty"`
	ReuseHomedir      bool   `ini:"reuse_homedir,omitempty"`
	UserAddCmd        string `ini:"useradd_cmd,omitempty"`
	UserDelCmd        string `ini:"userdel_cmd,omitempty"`
}

// AddressManager contains the configuration of addressManager section.
type AddressManager struct {
	Disable bool `ini:"disable,omitempty"`
}

// Daemons contains the configurations of Daemons section.
type Daemons struct {
	AccountsDaemon  bool `ini:"accounts_daemon,omitempty"`
	ClockSkewDaemon bool `ini:"clock_skew_daemon,omitempty"`
	NetworkDaemon   bool `ini:"network_daemon,omitempty"`
}

// Diagnostics contains the configurations of Diagnostics section.
type Diagnostics struct {
	Enable bool `ini:"enable,omitempty"`
}

// IPForwarding contains the configurations of IPForwarding section.
type IPForwarding struct {
	EthernetProtoID   string `ini:"ethernet_proto_id,omitempty"`
	IPAliases         bool   `ini:"ip_aliases,omitempty"`
	TargetInstanceIPs bool   `ini:"target_instance_ips,omitempty"`
}

// Instance contains the configurations of Instance section.
type Instance struct {
	// InstanceID is a backward compatible key. In the past the instance id was only
	// supported/setup via config file, if we can't read the instance_id file then
	// try honoring this configuration key.
	InstanceID string `ini:"instance_id,omitempty"`

	// InstanceIDDir defines where the instance id file should be read from.
	InstanceIDDir string `ini:"instance_id_dir,omitempty"`
}

// InstanceSetup contains the configurations of InstanceSetup section.
type InstanceSetup struct {
	HostKeyDir       string `ini:"host_key_dir,omitempty"`
	HostKeyTypes     string `ini:"host_key_types,omitempty"`
	NetworkEnabled   bool   `ini:"network_enabled,omitempty"`
	OptimizeLocalSSD bool   `ini:"optimize_local_ssd,omitempty"`
	SetBotoConfig    bool   `ini:"set_boto_config,omitempty"`
	SetHostKeys      bool   `ini:"set_host_keys,omitempty"`
	SetMultiqueue    bool   `ini:"set_multiqueue,omitempty"`
}

// MetadataScripts contains the configurations of MetadataScripts section.
type MetadataScripts struct {
	DefaultShell      string `ini:"default_shell,omitempty"`
	RunDir            string `ini:"run_dir,omitempty"`
	Shutdown          bool   `ini:"shutdown,omitempty"`
	ShutdownWindows   bool   `ini:"shutdown-windows,omitempty"`
	Startup           bool   `ini:"startup,omitempty"`
	StartupWindows    bool   `ini:"startup-windows,omitempty"`
	SysprepSpecialize bool   `ini:"sysprep_specialize,omitempty"`
}

// OSLogin contains the configurations of OSLogin section.
type OSLogin struct {
	CertAuthentication bool `ini:"cert_authentication,omitempty"`
}

// MDS contains the configurations for MDS section. Currently its opt-in only
// and should not assume any defaults. Setting any [defaultConfig] values will
// override user settings from Metadata.
type MDS struct {
	// DisableHTTPSMdsSetup enables/disables the mTLS credential refresher.
	DisableHTTPSMdsSetup bool `ini:"disable-https-mds-setup,omitempty"`
	// HTTPSMDSEnableNativeStore enables/disables the use of OSs native store. Native
	// store is Certificate Store on Windows which hosts both Client Credential and
	// Root certificate where as its trust store that hosts root certs like
	// `/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem` on Linux.
	HTTPSMDSEnableNativeStore bool `ini:"enable-https-mds-native-cert-store,omitempty"`
}

// NetworkInterfaces contains the configurations of NetworkInterfaces section.
type NetworkInterfaces struct {
	DHCPCommand                  string `ini:"dhcp_command,omitempty"`
	IPForwarding                 bool   `ini:"ip_forwarding,omitempty"`
	Setup                        bool   `ini:"setup,omitempty"`
	ManagePrimaryNIC             bool   `ini:"manage_primary_nic,omitempty"`
	RestoreDebian12NetplanConfig bool   `ini:"restore_debian12_netplan_config,omitempty"`
	VlanSetupEnabled             bool   `ini:"vlan_setup_enabled,omitempty"`
}

// Routes contains the configurations of the Routes section.
type Routes struct {
	EnableMonitor   bool   `ini:"enable_monitor,omitempty"`
	MonitorInterval string `ini:"monitor_interval,omitempty"`
}

// Snapshots contains the configurations of Snapshots section.
type Snapshots struct {
	Enabled             bool   `ini:"enabled,omitempty"`
	SnapshotServiceIP   string `ini:"snapshot_service_ip,omitempty"`
	SnapshotServicePort int    `ini:"snapshot_service_port,omitempty"`
	TimeoutInSeconds    int    `ini:"timeout_in_seconds,omitempty"`
}

// Unstable contains the configurations of Unstable section. No long term stability or support
// is guaranteed for configurations defined in the Unstable section. By default all flags defined
// in this section is disabled and is intended to isolate under development features.
type Unstable struct {
	CommandMonitorEnabled bool   `ini:"command_monitor_enabled,omitempty"`
	CommandPipePath       string `ini:"command_pipe_path,omitempty"`
	CommandRequestTimeout string `ini:"command_request_timeout,omitempty"`
	CommandPipeMode       string `ini:"command_pipe_mode,omitempty"`
	CommandPipeGroup      string `ini:"command_pipe_group,omitempty"`
	SystemdConfigDir      string `ini:"systemd_config_dir,omitempty"`
}

// WSFC contains the configurations of WSFC section.
type WSFC struct {
	Addresses string `ini:"addresses,omitempty"`
	Enable    bool   `ini:"enable,omitempty"`
	Port      string `ini:"port,omitempty"`
}

func defaultConfigFile(osName string) string {
	if osName == "windows" {
		return winConfigPath
	}
	return unixConfigPath
}

func defaultDataSources(extraDefaults []byte) []interface{} {
	var defaultConf string

	switch os := runtime.GOOS; os {
	case "freebsd":
		defaultConf = defaultConfigFreeBSD
	default:
		defaultConf = defaultConfig
	}

	var res = []interface{}{[]byte(defaultConf)}
	config := configFile(runtime.GOOS)

	if len(extraDefaults) > 0 {
		res = append(res, extraDefaults)
	}

	return append(res, []interface{}{
		config,
		config + ".distro",
		config + ".template",
	}...)
}

// Load loads default configuration and the configuration from default config files.
func Load(extraDefaults []byte) error {
	opts := ini.LoadOptions{
		Loose:       true,
		Insensitive: true,
	}

	sources := dataSources(extraDefaults)
	cfg, err := ini.LoadSources(opts, sources[0], sources[1:]...)
	if err != nil {
		return fmt.Errorf("failed to load configuration: %+v", err)
	}

	sections := new(Sections)
	if err := cfg.MapTo(sections); err != nil {
		return fmt.Errorf("failed to map configuration to object: %+v", err)
	}

	instance = sections
	return nil
}

// Get returns the configuration's instance previously loaded with Load().
func Get() *Sections {
	if instance == nil {
		panic("cfg package was not initialized, Load() " +
			"should be called in the early initialization code path")
	}
	return instance
}
