/*
Copyright 2019 The Kubernetes Authors.

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 vfsclientset

import (
	"context"
	"fmt"
	"strings"

	"k8s.io/klog/v2"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/apis/kops/registry"
	kopsinternalversion "k8s.io/kops/pkg/client/clientset_generated/clientset/typed/kops/internalversion"
	"k8s.io/kops/pkg/client/simple"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/secrets"
	"k8s.io/kops/util/pkg/vfs"
)

type VFSClientset struct {
	vfsContext *vfs.VFSContext
	basePath   vfs.Path
}

var _ simple.Clientset = &VFSClientset{}

func (c *VFSClientset) VFSContext() *vfs.VFSContext {
	return c.vfsContext
}

func (c *VFSClientset) clusters() *ClusterVFS {
	return newClusterVFS(c.VFSContext(), c.basePath)
}

// GetCluster implements the GetCluster method of simple.Clientset for a VFS-backed state store
func (c *VFSClientset) GetCluster(ctx context.Context, name string) (*kops.Cluster, error) {
	ctx, span := tracer.Start(ctx, "VFSClientset::GetCluster")
	defer span.End()

	return c.clusters().Get(ctx, name, metav1.GetOptions{})
}

// UpdateCluster implements the UpdateCluster method of simple.Clientset for a VFS-backed state store
func (c *VFSClientset) UpdateCluster(ctx context.Context, cluster *kops.Cluster, status *kops.ClusterStatus) (*kops.Cluster, error) {
	return c.clusters().Update(cluster, status)
}

// CreateCluster implements the CreateCluster method of simple.Clientset for a VFS-backed state store
func (c *VFSClientset) CreateCluster(ctx context.Context, cluster *kops.Cluster) (*kops.Cluster, error) {
	return c.clusters().Create(cluster)
}

// ListClusters implements the ListClusters method of simple.Clientset for a VFS-backed state store
func (c *VFSClientset) ListClusters(ctx context.Context, options metav1.ListOptions) (*kops.ClusterList, error) {
	ctx, span := tracer.Start(ctx, "VFSClientset::ListClusters")
	defer span.End()

	return c.clusters().List(ctx, options)
}

// ConfigBaseFor implements the ConfigBaseFor method of simple.Clientset for a VFS-backed state store
func (c *VFSClientset) ConfigBaseFor(cluster *kops.Cluster) (vfs.Path, error) {
	if cluster.Spec.ConfigStore.Base != "" {
		return c.VFSContext().BuildVfsPath(cluster.Spec.ConfigStore.Base)
	}
	return c.clusters().configBase(cluster.Name)
}

// InstanceGroupsFor implements the InstanceGroupsFor method of simple.Clientset for a VFS-backed state store
func (c *VFSClientset) InstanceGroupsFor(cluster *kops.Cluster) kopsinternalversion.InstanceGroupInterface {
	return newInstanceGroupVFS(c, cluster)
}

func (c *VFSClientset) AddonsFor(cluster *kops.Cluster) simple.AddonsClient {
	return newAddonsVFS(c, cluster)
}

func (c *VFSClientset) SecretStore(cluster *kops.Cluster) (fi.SecretStore, error) {
	if cluster.Spec.ConfigStore.Secrets == "" {
		configBase, err := registry.ConfigBase(c.VFSContext(), cluster)
		if err != nil {
			return nil, err
		}
		basedir := configBase.Join("secrets")
		return secrets.NewVFSSecretStore(cluster, basedir), nil
	} else {
		storePath, err := c.VFSContext().BuildVfsPath(cluster.Spec.ConfigStore.Secrets)
		return secrets.NewVFSSecretStore(cluster, storePath), err
	}
}

func (c *VFSClientset) KeyStore(cluster *kops.Cluster) (fi.CAStore, error) {
	basedir, err := c.pkiPath(cluster)
	if err != nil {
		return nil, err
	}

	klog.V(8).Infof("Using keystore path: %q", basedir)

	return fi.NewVFSCAStore(cluster, basedir), err
}

func (c *VFSClientset) SSHCredentialStore(cluster *kops.Cluster) (fi.SSHCredentialStore, error) {
	basedir, err := c.pkiPath(cluster)
	if err != nil {
		return nil, err
	}
	return fi.NewVFSSSHCredentialStore(cluster, basedir), nil
}

func (c *VFSClientset) pkiPath(cluster *kops.Cluster) (vfs.Path, error) {
	if cluster.Spec.ConfigStore.Keypairs == "" {
		configBase, err := registry.ConfigBase(c.VFSContext(), cluster)
		if err != nil {
			return nil, err
		}
		return configBase.Join("pki"), nil
	} else {
		storePath, err := c.VFSContext().BuildVfsPath(cluster.Spec.ConfigStore.Keypairs)
		return storePath, err
	}
}

func DeleteAllClusterState(ctx context.Context, basePath vfs.Path) error {
	paths, err := basePath.ReadTree(ctx)
	if err != nil {
		return fmt.Errorf("error listing files in state store: %v", err)
	}

	for _, path := range paths {
		relativePath, err := vfs.RelativePath(basePath, path)
		if err != nil {
			return err
		}

		if relativePath == "" {
			continue
		}

		// "cluster.spec" was written by kOps 1.21 and earlier.
		if relativePath == "config" || relativePath == "cluster.spec" || relativePath == "cluster-completed.spec" || relativePath == registry.PathKopsVersionUpdated {
			continue
		}
		if strings.HasPrefix(relativePath, "addons/") {
			continue
		}
		if strings.HasPrefix(relativePath, "clusteraddons/") {
			continue
		}
		if strings.HasPrefix(relativePath, "pki/") {
			continue
		}
		if strings.HasPrefix(relativePath, "secrets/") {
			continue
		}
		if strings.HasPrefix(relativePath, "instancegroup/") {
			continue
		}
		if strings.HasPrefix(relativePath, "igconfig/") {
			continue
		}
		if strings.HasPrefix(relativePath, "manifests/") {
			continue
		}
		// TODO: offer an option _not_ to delete backups?
		if strings.HasPrefix(relativePath, "backups/") {
			continue
		}

		return fmt.Errorf("refusing to delete: unknown file found: %s", path)
	}

	err = basePath.RemoveAll(ctx)
	if err != nil {
		return fmt.Errorf("error deleting cluster files in %s: %w", basePath, err)
	}

	return nil
}

func (c *VFSClientset) DeleteCluster(ctx context.Context, cluster *kops.Cluster) error {
	if cluster.Spec.ServiceAccountIssuerDiscovery != nil {
		discoveryStore := cluster.Spec.ServiceAccountIssuerDiscovery.DiscoveryStore
		if discoveryStore != "" {
			path, err := c.VFSContext().BuildVfsPath(discoveryStore)
			if err != nil {
				return err
			}

			err = path.Join("openid/v1/jwks").Remove(ctx)
			if err != nil {
				return err
			}
			err = path.Join(".well-known/openid-configuration").Remove(ctx)
			if err != nil {
				return err
			}
		}

		if discoveryService := cluster.Spec.ServiceAccountIssuerDiscovery.DiscoveryService; discoveryService != nil {
			// TODO: implement deletion of discovery service resources
			klog.Warningf("automatic cleanup of discovery service resources not yet implemented")
		}
	}

	secretStore := cluster.Spec.ConfigStore.Secrets
	if secretStore != "" {
		path, err := c.VFSContext().BuildVfsPath(secretStore)
		if err != nil {
			return err
		}
		err = path.RemoveAll(ctx)
		if err != nil {
			return err
		}
	}

	keyStore := cluster.Spec.ConfigStore.Keypairs
	if keyStore != "" && keyStore != secretStore {
		path, err := c.VFSContext().BuildVfsPath(keyStore)
		if err != nil {
			return err
		}
		err = path.RemoveAll(ctx)
		if err != nil {
			return err
		}
	}

	configBase, err := registry.ConfigBase(c.VFSContext(), cluster)
	if err != nil {
		return err
	}

	return DeleteAllClusterState(ctx, configBase)
}

func NewVFSClientset(vfsContext *vfs.VFSContext, basePath vfs.Path) simple.Clientset {
	vfsClientset := &VFSClientset{
		vfsContext: vfsContext,
		basePath:   basePath,
	}
	return vfsClientset
}
