/*
Copyright 2021 The KubeVela 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 plugins

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/oam-dev/kubevela/apis/types"
	"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
	"github.com/oam-dev/kubevela/pkg/utils/common"
	"github.com/oam-dev/kubevela/pkg/utils/system"
)

// LoadCapabilityByName will load capability from local by name
func LoadCapabilityByName(name string, userNamespace string, c common.Args) (types.Capability, error) {
	caps, err := LoadAllInstalledCapability(userNamespace, c)
	if err != nil {
		return types.Capability{}, err
	}
	for _, c := range caps {
		if c.Name == name {
			return c, nil
		}
	}
	return types.Capability{}, fmt.Errorf("%s not found", name)
}

// LoadAllInstalledCapability will list all capability
func LoadAllInstalledCapability(userNamespace string, c common.Args) ([]types.Capability, error) {
	caps, err := GetCapabilitiesFromCluster(context.TODO(), userNamespace, c, nil)
	if err != nil {
		return nil, err
	}
	systemCaps, err := GetCapabilitiesFromCluster(context.TODO(), types.DefaultKubeVelaNS, c, nil)
	if err != nil {
		return nil, err
	}
	caps = append(caps, systemCaps...)
	return caps, nil
}

// LoadInstalledCapabilityWithType will load cap list by type
func LoadInstalledCapabilityWithType(userNamespace string, c common.Args, capT types.CapType) ([]types.Capability, error) {
	switch capT {
	case types.TypeComponentDefinition:
		caps, _, err := GetComponentsFromCluster(context.TODO(), userNamespace, c, nil)
		if err != nil {
			return nil, err
		}
		if userNamespace != types.DefaultKubeVelaNS {
			systemCaps, _, err := GetComponentsFromCluster(context.TODO(), types.DefaultKubeVelaNS, c, nil)
			if err != nil {
				return nil, err
			}
			caps = append(caps, systemCaps...)
		}
		return caps, nil
	case types.TypeTrait:
		caps, _, err := GetTraitsFromCluster(context.TODO(), userNamespace, c, nil)
		if err != nil {
			return nil, err
		}
		if userNamespace != types.DefaultKubeVelaNS {
			systemCaps, _, err := GetTraitsFromCluster(context.TODO(), types.DefaultKubeVelaNS, c, nil)
			if err != nil {
				return nil, err
			}
			caps = append(caps, systemCaps...)
		}
		return caps, nil
	case types.TypeScope:
	case types.TypeWorkload:
	default:
	}
	return nil, nil
}

// GetInstalledCapabilityWithCapName will get cap by alias
func GetInstalledCapabilityWithCapName(capT types.CapType, capName string) (types.Capability, error) {
	dir, err := system.GetCapabilityDir()
	if err != nil {
		return types.Capability{}, err
	}
	return loadInstalledCapabilityWithCapName(dir, capT, capName)
}

// leave dir as argument for test convenience
func loadInstalledCapabilityWithType(dir string, capT types.CapType) ([]types.Capability, error) {
	dir = GetSubDir(dir, capT)
	return loadInstalledCapability(dir, "")
}

func loadInstalledCapabilityWithCapName(dir string, capT types.CapType, capName string) (types.Capability, error) {
	var cap types.Capability
	dir = GetSubDir(dir, capT)
	capList, err := loadInstalledCapability(dir, capName)
	if err != nil {
		return cap, err
	} else if len(capList) != 1 {
		return cap, fmt.Errorf("could not get installed capability by %s", capName)
	}
	return capList[0], nil
}

func loadInstalledCapability(dir string, name string) ([]types.Capability, error) {
	var tmps []types.Capability
	files, err := os.ReadDir(dir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	for _, f := range files {
		if f.IsDir() {
			continue
		}
		if strings.HasSuffix(f.Name(), ".cue") {
			continue
		}
		data, err := os.ReadFile(filepath.Clean(filepath.Join(dir, f.Name())))
		if err != nil {
			fmt.Printf("read file %s err %v\n", f.Name(), err)
			continue
		}
		var tmp types.Capability
		decoder := json.NewDecoder(bytes.NewBuffer(data))
		decoder.UseNumber()
		if err = decoder.Decode(&tmp); err != nil {
			fmt.Printf("ignore invalid format file: %s\n", f.Name())
			continue
		}
		// Get the specified installed capability: workload or trait
		if name != "" {
			if name == f.Name() {
				tmps = append(tmps, tmp)
				break
			}
			continue
		}
		tmps = append(tmps, tmp)
	}
	return tmps, nil
}

// GetSubDir will get dir for capability
func GetSubDir(dir string, capT types.CapType) string {
	switch capT {
	case types.TypeWorkload:
		return filepath.Join(dir, "workloads")
	case types.TypeTrait:
		return filepath.Join(dir, "traits")
	case types.TypeScope:
		return filepath.Join(dir, "scopes")
	case types.TypeComponentDefinition:
		return filepath.Join(dir, "components")
	default:
	}
	return dir
}

// SinkTemp2Local will sink template to local file
func SinkTemp2Local(templates []types.Capability, dir string) int {
	success := 0
	for _, tmp := range templates {
		subDir := GetSubDir(dir, tmp.Type)
		_, _ = system.CreateIfNotExist(subDir)
		data, err := json.Marshal(tmp)
		if err != nil {
			fmt.Printf("sync %s err: %v\n", tmp.Name, err)
			continue
		}
		//nolint:gosec
		err = os.WriteFile(filepath.Join(subDir, tmp.Name), data, 0644)
		if err != nil {
			fmt.Printf("sync %s err: %v\n", tmp.Name, err)
			continue
		}
		success++
	}
	return success
}

// RemoveLegacyTemps will remove capability definitions under `dir` but not included in `retainedTemps`.
func RemoveLegacyTemps(retainedTemps []types.Capability, dir string) int {
	success := 0
	var retainedFiles []string
	subDirs := []string{GetSubDir(dir, types.TypeComponentDefinition), GetSubDir(dir, types.TypeTrait)}
	for _, tmp := range retainedTemps {
		subDir := GetSubDir(dir, tmp.Type)
		tmpFilePath := filepath.Join(subDir, tmp.Name)
		retainedFiles = append(retainedFiles, tmpFilePath)
	}

	for _, subDir := range subDirs {
		if err := filepath.Walk(subDir, func(path string, info os.FileInfo, err error) error {
			if info == nil || info.IsDir() {
				// omit subDir or subDir not exist
				return nil
			}
			for _, retainedFile := range retainedFiles {
				if retainedFile == path {
					return nil
				}
			}
			if err := os.Remove(path); err != nil {
				fmt.Printf("remove legacy %s err: %v\n", path, err)
				return err
			}
			success++
			return nil
		}); err != nil {
			continue
		}
	}
	return success
}

// LoadCapabilityFromSyncedCenter will load capability from dir
func LoadCapabilityFromSyncedCenter(mapper discoverymapper.DiscoveryMapper, dir string) ([]types.Capability, error) {
	var tmps []types.Capability
	files, err := os.ReadDir(dir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	for _, f := range files {
		if f.IsDir() {
			continue
		}
		if strings.HasSuffix(f.Name(), ".cue") {
			continue
		}
		data, err := os.ReadFile(filepath.Clean(filepath.Join(dir, f.Name())))
		if err != nil {
			fmt.Printf("read file %s err %v\n", f.Name(), err)
			continue
		}
		tmp, err := ParseCapability(mapper, data)
		if err != nil {
			fmt.Printf("get definition of %s err %v\n", f.Name(), err)
			continue
		}
		tmps = append(tmps, tmp)
	}
	return tmps, nil
}
