//  Copyright 2024 Google LLC
//
//  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
//
//     https://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 metadata

import (
	"encoding/json"
	"fmt"

	"github.com/GoogleCloudPlatform/google-guest-agent/internal/plugin/config"
	"github.com/GoogleCloudPlatform/google-guest-agent/internal/utils/ssh"
)

// Descriptor wraps/holds all the metadata keys, the structure reflects the json
// descriptor returned with metadata call with alt=json. This is a read-only
// wrapper of the internal representation.
type Descriptor struct {
	instance *Instance
	project  *Project
	universe *Universe
}

// Instance describes the metadata's instance attributes/keys. This is a
// read-only wrapper of the internal representation.
type Instance struct {
	internal              *instance
	attributes            *Attributes
	networkInterfaces     []*NetworkInterface
	vlanInterfaces        []map[int]*VlanInterface
	virtualClock          *VirtualClock
	identityConfiguration *IdentityConfiguration
}

// Universe describes the metadata's universe attributes/keys. This is a
// read-only wrapper of the internal representation.
type Universe struct {
	internal *gcpUniverse
}

// VirtualClock describes the clock skew's configuration.
type VirtualClock struct {
	internal *virtualClock
}

// NetworkInterface describes the instances network interfaces configurations.
// This is a read-only wrapper of the internal representation.
type NetworkInterface struct {
	internal *networkInterface
}

// VlanInterface describes the instances vlan interface configurations.
// This is a read-only wrapper of the internal representation.
type VlanInterface struct {
	internal *vlanInterface
}

// Project describes the projects instance's attributes. This is a read-only
// wrapper of the internal representation.
type Project struct {
	internal   *project
	attributes *Attributes
}

// IdentityConfiguration describes the instances identity configuration.
// This is a read-only wrapper of the internal representation.
type IdentityConfiguration struct {
	internal *identityConfiguration
}

// Attributes describes the project's attributes keys. This is a read-only
// wrapper of the internal representation.
type Attributes struct {
	internal    *attributes
	windowsKeys []*WindowsKey
}

// WindowsKey describes the WindowsKey metadata keys. This is a read-only
// wrapper of the internal representation.
type WindowsKey struct {
	internal *windowsKey
}

func newDescriptor(descriptor descriptor) *Descriptor {
	return &Descriptor{
		instance: newInstance(descriptor.Instance),
		project:  newProject(descriptor.Project),
		universe: newUniverse(descriptor.Universe),
	}
}

// AccountManagerDisabled reports whether instance and project metadata
// indicates that windows accounts manager should be disabled.
func (desc *Descriptor) AccountManagerDisabled() bool {
	if desc.Instance().Attributes().DisableAccountManager() != nil {
		return *desc.Instance().Attributes().DisableAccountManager()
	}
	if desc.Project().Attributes().DisableAccountManager() != nil {
		return *desc.Project().Attributes().DisableAccountManager()
	}
	return false
}

// AddressManagerDisabled reports whether instance and project metadata
// indicates that address manager should be disabled.
func (desc *Descriptor) AddressManagerDisabled() bool {
	if desc.Instance().Attributes().DisableAddressManager() != nil {
		return *desc.Instance().Attributes().DisableAddressManager()
	}
	if desc.Project().Attributes().DisableAddressManager() != nil {
		return *desc.Project().Attributes().DisableAddressManager()
	}
	return false
}

// OSLoginEnabled reports whether instance and project metadata attributes
// indicate that OSLogin should be enabled.
func (desc *Descriptor) OSLoginEnabled() bool {
	var enable bool
	if desc.Project().Attributes().EnableOSLogin() != nil {
		enable = *desc.Project().Attributes().EnableOSLogin()
	}
	if desc.Instance().Attributes().EnableOSLogin() != nil {
		enable = *desc.Instance().Attributes().EnableOSLogin()
	}
	return enable
}

// TwoFactorEnabled returns true if two factor authentication is enabled for
// OSLogin.
func (desc *Descriptor) TwoFactorEnabled() bool {
	var enabled bool

	// Check project level first allowing instance level to override.
	if desc.Project().Attributes().TwoFactor() != nil {
		enabled = *desc.Project().Attributes().TwoFactor()
	}

	// Instance level takes precedence over project level.
	if desc.Instance().Attributes().TwoFactor() != nil {
		enabled = *desc.Instance().Attributes().TwoFactor()
	}

	return enabled
}

// SecurityKeyEnabled returns true if security key is enabled for OSLogin.
func (desc *Descriptor) SecurityKeyEnabled() bool {
	var enabled bool

	// Check project level first allowing instance level to override.
	if desc.Project().Attributes().SecurityKey() != nil {
		enabled = *desc.Project().Attributes().SecurityKey()
	}

	// Instance level takes precedence over project level.
	if desc.Instance().Attributes().SecurityKey() != nil {
		enabled = *desc.Instance().Attributes().SecurityKey()
	}

	return enabled
}

// CertRequiredEnabled returns true if cert required is enabled for OSLogin.
func (desc *Descriptor) CertRequiredEnabled() bool {
	var enabled bool

	// Check project level first allowing instance level to override.
	if desc.Project().Attributes().RequireCerts() != nil {
		enabled = *desc.Project().Attributes().RequireCerts()
	}

	// Instance level takes precedence over project level.
	if desc.Instance().Attributes().RequireCerts() != nil {
		enabled = *desc.Instance().Attributes().RequireCerts()
	}

	return enabled
}

// WindowsSSHEnabled returns true if instance's or project's attributes has the
// enable-windows-ssh key is defined and set to true.
func (desc *Descriptor) WindowsSSHEnabled() bool {
	instance := desc.Instance()
	project := desc.Project()

	// Instance's attribute takes precedence over projects.
	if instance != nil && instance.Attributes().EnableWindowsSSH() != nil {
		return bool(*instance.Attributes().EnableWindowsSSH())
	}

	// If the instance attribute doesn't enable it we can still have it enabled
	// via projects attributes.
	if project != nil && project.Attributes().EnableWindowsSSH() != nil {
		return bool(*project.Attributes().EnableWindowsSSH())
	}

	return false
}

// UserSSHKeys returns the user's SSH keys.
func (desc *Descriptor) UserSSHKeys(username string) ([]string, error) {
	var userKeys []string

	if desc.Instance() == nil || desc.Project() == nil {
		return userKeys, fmt.Errorf("USERSSHKeys() instance or project is nil")
	}

	instanceAttrs := desc.Instance().Attributes()
	instanceKeyList := ssh.ParseKeys(username, instanceAttrs.SSHKeys())
	userKeys = append(userKeys, instanceKeyList...)

	if !instanceAttrs.BlockProjectKeys() {
		projectAttrs := desc.Project().Attributes()
		projectKeyList := ssh.ParseKeys(username, projectAttrs.SSHKeys())
		userKeys = append(userKeys, projectKeyList...)
	}

	return userKeys, nil
}

// Instance returns the instance described by metadata server.
func (desc *Descriptor) Instance() *Instance {
	return desc.instance
}

func newInstance(instance instance) *Instance {
	res := &Instance{internal: &instance}
	for _, nic := range instance.NetworkInterfaces {
		res.networkInterfaces = append(res.networkInterfaces, newNetworkInterface(nic))
	}
	for _, vics := range instance.VlanNetworkInterfaces {
		mp := make(map[int]*VlanInterface)
		for vlanID, iface := range vics {
			mp[vlanID] = newVlanInterface(iface)
		}
		res.vlanInterfaces = append(res.vlanInterfaces, mp)
	}
	res.attributes = newAttributes(instance.Attributes)
	res.virtualClock = newVirtualClock(instance.VirtualClock)
	res.identityConfiguration = newIdentityConfiguration(instance.IdentityConfiguration)
	return res
}

// IdentityConfiguration returns the instance identity configuration.
func (in *Instance) IdentityConfiguration() *IdentityConfiguration {
	return in.identityConfiguration
}

// ID returns the instance ID.
func (in *Instance) ID() json.Number {
	return in.internal.ID
}

// Name returns the instance name.
func (in *Instance) Name() string {
	return in.internal.Name
}

// MachineType returns the instance machine type.
func (in *Instance) MachineType() string {
	return in.internal.MachineType
}

// Attributes returns the instance attributes.
func (in *Instance) Attributes() *Attributes {
	return in.attributes
}

// NetworkInterfaces returns the instance network interfaces.
func (in *Instance) NetworkInterfaces() []*NetworkInterface {
	return in.networkInterfaces
}

// VlanInterfaces returns the instance vlan interfaces.
func (in *Instance) VlanInterfaces() []map[int]*VlanInterface {
	return in.vlanInterfaces
}

// VirtualClock returns the instance virtual clock.
func (in *Instance) VirtualClock() *VirtualClock {
	return in.virtualClock
}

// Universe returns the universe described by metadata server.
func (desc *Descriptor) Universe() *Universe {
	return desc.universe
}

// newUniverse returns the universe described by metadata server.
func newUniverse(universe gcpUniverse) *Universe {
	return &Universe{internal: &universe}
}

// UniverseDomain returns the universe domain.
func (u *Universe) UniverseDomain() string {
	if u.internal.UniverseDomain == "" {
		return DefaultUniverseDomain
	}
	return u.internal.UniverseDomain
}

func newVirtualClock(vc virtualClock) *VirtualClock {
	return &VirtualClock{internal: &vc}
}

// DriftToken returns the virtual clock drift token.
func (vc *VirtualClock) DriftToken() string {
	return vc.internal.DriftToken
}

// Project returns the project described by metadata server.
func (desc *Descriptor) Project() *Project {
	return desc.project
}

func newProject(project project) *Project {
	return &Project{
		internal:   &project,
		attributes: newAttributes(project.Attributes),
	}
}

// Attributes returns the project attributes.
func (proj *Project) Attributes() *Attributes {
	return proj.attributes
}

// ID returns the project ID.
func (proj *Project) ID() string {
	return proj.internal.ID
}

// NumericProjectID returns the project ID.
func (proj *Project) NumericProjectID() json.Number {
	return proj.internal.NumericProjectID
}

func newNetworkInterface(nic networkInterface) *NetworkInterface {
	return &NetworkInterface{internal: &nic}
}

// ForwardedIPs returns the instance network interfaces forwarded IPs.
func (nic *NetworkInterface) ForwardedIPs() []string {
	return nic.internal.ForwardedIps
}

// ForwardedIPv6s returns the instance network interfaces forwarded IPv6s.
func (nic *NetworkInterface) ForwardedIPv6s() []string {
	return nic.internal.ForwardedIpv6s
}

// TargetInstanceIPs returns the instance network interfaces target instance
// IPs.
func (nic *NetworkInterface) TargetInstanceIPs() []string {
	return nic.internal.TargetInstanceIps
}

// IPAliases returns the instance network interfaces IP aliases.
func (nic *NetworkInterface) IPAliases() []string {
	return nic.internal.IPAliases
}

// MAC returns the instance network interfaces MAC address.
func (nic *NetworkInterface) MAC() string {
	return nic.internal.Mac
}

// DHCPv6Refresh returns the instance network interfaces DHCPv6 refresh.
func (nic *NetworkInterface) DHCPv6Refresh() string {
	return nic.internal.DHCPv6Refresh
}

func newVlanInterface(nic vlanInterface) *VlanInterface {
	return &VlanInterface{internal: &nic}
}

// MAC returns the instance vlan interfaces MAC address.
func (vic *VlanInterface) MAC() string {
	return vic.internal.Mac
}

// ParentInterface returns the instance vlan interfaces parent interface.
func (vic *VlanInterface) ParentInterface() string {
	return vic.internal.ParentInterface
}

// Vlan returns the instance vlan interfaces vlan.
func (vic *VlanInterface) Vlan() int {
	return vic.internal.Vlan
}

// MTU returns the instance vlan interfaces MTU.
func (vic *VlanInterface) MTU() int {
	return vic.internal.MTU
}

// IPAddress returns the instance vlan interfaces IP address.
func (vic *VlanInterface) IPAddress() string {
	return vic.internal.IP
}

// IPv6Addresses returns the instance vlan interfaces IPv6 addresses.
func (vic *VlanInterface) IPv6Addresses() []string {
	return vic.internal.IPv6
}

// Gateway returns the instance vlan interfaces gateway.
func (vic *VlanInterface) Gateway() string {
	return vic.internal.Gateway
}

// GatewayIPv6 returns the instance vlan interfaces gateway IPv6.
func (vic *VlanInterface) GatewayIPv6() string {
	return vic.internal.GatewayIPv6
}

func newIdentityConfiguration(ic identityConfiguration) *IdentityConfiguration {
	return &IdentityConfiguration{internal: &ic}
}

// IdentityUUID returns the instance identity UUID.
func (i *IdentityConfiguration) IdentityUUID() string {
	return i.internal.IdentityUUID
}

func newAttributes(attributes attributes) *Attributes {
	res := &Attributes{internal: &attributes}
	for _, windowsKey := range attributes.WindowsKeys {
		res.windowsKeys = append(res.windowsKeys, &WindowsKey{internal: &windowsKey})
	}
	return res
}

// CreatedBy returns the attribute created-by.
func (attr *Attributes) CreatedBy() string {
	return attr.internal.CreatedBy
}

// Hostname returns the attribute hostname.
func (attr *Attributes) Hostname() string {
	return attr.internal.Hostname
}

// BlockProjectKeys returns the attributes block project keys.
func (attr *Attributes) BlockProjectKeys() bool {
	return attr.internal.BlockProjectKeys
}

// EnableOSLogin returns the attributes enable OS login.
func (attr *Attributes) EnableOSLogin() *bool {
	return attr.internal.EnableOSLogin
}

// EnableWindowsSSH returns the attributes enable windows ssh.
func (attr *Attributes) EnableWindowsSSH() *bool {
	return attr.internal.EnableWindowsSSH
}

// TwoFactor returns the attributes two factor.
func (attr *Attributes) TwoFactor() *bool {
	return attr.internal.TwoFactor
}

// SecurityKey returns the attributes security key.
func (attr *Attributes) SecurityKey() *bool {
	return attr.internal.SecurityKey
}

// RequireCerts returns the attributes require certs.
func (attr *Attributes) RequireCerts() *bool {
	return attr.internal.RequireCerts
}

// SSHKeys returns the attributes SSH keys.
func (attr *Attributes) SSHKeys() []string {
	return attr.internal.SSHKeys
}

// WindowsKeys returns the attributes windows keys.
func (attr *Attributes) WindowsKeys() []*WindowsKey {
	return attr.windowsKeys
}

// Diagnostics returns the attributes diagnostics.
func (attr *Attributes) Diagnostics() string {
	return attr.internal.Diagnostics
}

// DisableAddressManager returns the attributes disable address
// manager.
func (attr *Attributes) DisableAddressManager() *bool {
	return attr.internal.DisableAddressManager
}

// DisableAccountManager returns the attributes disable account
// manager.
func (attr *Attributes) DisableAccountManager() *bool {
	return attr.internal.DisableAccountManager
}

// EnableDiagnostics returns the attributes enable diagnostics.
func (attr *Attributes) EnableDiagnostics() *bool {
	return attr.internal.EnableDiagnostics
}

// EnableCorePlugin returns the attributes enable core plugin.
func (attr *Attributes) EnableCorePlugin() *bool {
	return attr.internal.EnableCorePlugin
}

// DisableHTTPSMdsSetup returns the attributes disable http-smds setup.
func (attr *Attributes) DisableHTTPSMdsSetup() *bool {
	return attr.internal.DisableHTTPSMdsSetup
}

// HTTPSMDSEnableNativeStore returns the attributes enable native store.
func (attr *Attributes) HTTPSMDSEnableNativeStore() *bool {
	return attr.internal.HTTPSMDSEnableNativeStore
}

// EnableWSFC returns the attributes enable wsfc.
func (attr *Attributes) EnableWSFC() *bool {
	return attr.internal.EnableWSFC
}

// WSFCAddresses returns the attributes wsfc addresses.
func (attr *Attributes) WSFCAddresses() string {
	return attr.internal.WSFCAddresses
}

// WSFCAgentPort returns the attributes wsfc agent port.
func (attr *Attributes) WSFCAgentPort() string {
	return attr.internal.WSFCAgentPort
}

// DisableTelemetry returns the attributes disable telemetry.
func (attr *Attributes) DisableTelemetry() bool {
	return attr.internal.DisableTelemetry
}

// DisableDNSProbe returns the attributes disable dns probe.
func (attr *Attributes) DisableDNSProbe() bool {
	return attr.internal.DisableDNSProbe
}

// Email is the windows email key.
func (wk *WindowsKey) Email() string {
	return wk.internal.Email
}

// ExpireOn is the windows expireOn key.
func (wk *WindowsKey) ExpireOn() string {
	return wk.internal.ExpireOn
}

// Exponent is the windows exponent key.
func (wk *WindowsKey) Exponent() string {
	return wk.internal.Exponent
}

// Modulus is the windows modulus key.
func (wk *WindowsKey) Modulus() string {
	return wk.internal.Modulus
}

// UserName is the windows userName key.
func (wk *WindowsKey) UserName() string {
	return wk.internal.UserName
}

// HashFunction is the windows hashFunction key.
func (wk *WindowsKey) HashFunction() string {
	return wk.internal.HashFunction
}

// AddToAdministrator is the windows addToAdministrator key.
func (wk *WindowsKey) AddToAdministrator() *bool {
	return wk.internal.AddToAdministrators
}

// PasswordLength is the windows passwordLength key.
func (wk *WindowsKey) PasswordLength() int {
	return wk.internal.PasswordLength
}

// HasServiceAccount returns true if the instance has service accounts attached.
func (desc *Descriptor) HasServiceAccount() bool {
	return len(desc.Instance().internal.ServiceAccounts) > 0
}

// HasCorePluginEnabled returns whether the Core Plugin is enabled or not based
// on MDS descriptor. If neither instance or project level metadata is set, it
// defaults to *true*.
func (desc *Descriptor) HasCorePluginEnabled() bool {
	if desc.Instance().Attributes().EnableCorePlugin() != nil {
		return *desc.Instance().Attributes().EnableCorePlugin()
	}
	if desc.Project().Attributes().EnableCorePlugin() != nil {
		return *desc.Project().Attributes().EnableCorePlugin()
	}

	return config.IsCorePluginEnabled()
}
