// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//

package compatoci

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"

	specs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/sirupsen/logrus"

	vcAnnotations "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/annotations"
)

var ociLog = logrus.WithFields(logrus.Fields{
	"source":    "virtcontainers",
	"subsystem": "compatoci",
})

// compatOCIProcess is a structure inheriting from specs.Process defined
// in runtime-spec/specs-go package. The goal is to be compatible with
// both v1.0.0-rc4 and v1.0.0-rc5 since the latter introduced a change
// about the type of the Capabilities field.
// Refer to: https://github.com/opencontainers/runtime-spec/commit/37391fb
type compatOCIProcess struct {
	Capabilities interface{} `json:"capabilities,omitempty" platform:"linux"` //nolint:govet
	specs.Process
}

// compatOCISpec is a structure inheriting from specs.Spec defined
// in runtime-spec/specs-go package. It relies on the compatOCIProcess
// structure declared above, in order to be compatible with both
// v1.0.0-rc4 and v1.0.0-rc5.
// Refer to: https://github.com/opencontainers/runtime-spec/commit/37391fb
type compatOCISpec struct {
	specs.Spec
	Process *compatOCIProcess `json:"process,omitempty"` //nolint:govet
}

func containerCapabilities(s compatOCISpec) (specs.LinuxCapabilities, error) {
	capabilities := s.Process.Capabilities
	var c specs.LinuxCapabilities

	// In spec v1.0.0-rc4, capabilities was a list of strings. This was changed
	// to an object with v1.0.0-rc5.
	// Check for the interface type to support both the versions.
	switch caps := capabilities.(type) {
	case map[string]interface{}:
		for key, value := range caps {
			switch val := value.(type) {
			case []interface{}:
				var list []string

				for _, str := range val {
					list = append(list, str.(string))
				}

				switch key {
				case "bounding":
					c.Bounding = list
				case "effective":
					c.Effective = list
				case "inheritable":
					c.Inheritable = list
				case "ambient":
					c.Ambient = list
				case "permitted":
					c.Permitted = list
				}

			default:
				return c, fmt.Errorf("unexpected format for capabilities: %v", caps)
			}
		}
	case []interface{}:
		var list []string
		for _, str := range caps {
			list = append(list, str.(string))
		}

		c = specs.LinuxCapabilities{
			Bounding:    list,
			Effective:   list,
			Inheritable: list,
			Ambient:     list,
			Permitted:   list,
		}
	case nil:
		ociLog.Debug("Empty capabilities have been passed")
		return c, nil
	default:
		return c, fmt.Errorf("unexpected format for capabilities: %v", caps)
	}

	return c, nil
}

// SetLogger sets up a logger for this pkg
func SetLogger(logger *logrus.Entry) {
	fields := ociLog.Data

	ociLog = logger.WithFields(fields)
}

// ContainerCapabilities return a LinuxCapabilities for virtcontainer
func ContainerCapabilities(s compatOCISpec) (specs.LinuxCapabilities, error) {
	if s.Process == nil {
		return specs.LinuxCapabilities{}, fmt.Errorf("ContainerCapabilities, Process is nil")
	}
	return containerCapabilities(s)
}

// getConfigPath returns the full config path from the bundle
// path provided.
func getConfigPath(bundlePath string) string {
	return filepath.Join(bundlePath, "config.json")
}

// ParseConfigJSON unmarshals the config.json file.
func ParseConfigJSON(bundlePath string) (specs.Spec, error) {
	configPath := getConfigPath(bundlePath)
	ociLog.Debugf("converting %s", configPath)

	configByte, err := os.ReadFile(configPath)
	if err != nil {
		return specs.Spec{}, err
	}

	var compSpec compatOCISpec
	if err := json.Unmarshal(configByte, &compSpec); err != nil {
		return specs.Spec{}, err
	}

	caps, err := ContainerCapabilities(compSpec)
	if err != nil {
		return specs.Spec{}, err
	}

	compSpec.Spec.Process = &compSpec.Process.Process
	compSpec.Spec.Process.Capabilities = &caps

	return compSpec.Spec, nil
}

func GetContainerSpec(annotations map[string]string) (specs.Spec, error) {
	if bundlePath, ok := annotations[vcAnnotations.BundlePathKey]; ok {
		return ParseConfigJSON(bundlePath)
	}

	ociLog.Errorf("Annotations[%s] not found, cannot find container spec",
		vcAnnotations.BundlePathKey)
	return specs.Spec{}, fmt.Errorf("could not find container spec")
}
