package plugin

import (
	"fmt"
	"io/ioutil"
	"regexp"

	"code.fbi.h-da.de/danet/gosdn/controller/customerrs"
	"github.com/google/uuid"
	"gopkg.in/yaml.v3"
)

// State represents the current state of a plugin within the controller. Since
// the plugins used within the controller are basic go plugins, they can be
// CREATED, BUILT, LOADED or FAULTY. A plugin can not be unloaded (this is a
// limitation of go plugins in general).
type State int64

const (
	//CREATED state describes a plugin which has been created but is not yet
	//built.
	CREATED State = iota
	// BUILT state describes a plugin which is built and can be loaded into the
	// controller.
	BUILT
	// LOADED state describes a plugin which is running within the controller.
	LOADED
	// FAULTY state describes a plugin which couldn't be built or loaded.
	FAULTY
)

// Plugin describes an interface for a plugin within the controller - a plugin
// in the context of the controller is a basic go plugin. A plugin satisfies
// the Storable interface and can be stored.
//
// Note(mbauch): Currently a plugin is built through the controller itself.
// This is fine for the time being, but should be reconsidered for the time to
// come. In the future we should provide a build environment that allows to
// build plugins within the same environment as the controller itself.
type Plugin interface {
	ID() uuid.UUID
	State() State
	Path() string
	Manifest() *Manifest
	Update() error
}

// Manifest represents the manifest of a plugin.
type Manifest struct {
	// Name of the plugin
	Name string `yaml:"name"`
	// Author of the plugin
	Author string `yaml:"author"`
	// Version of the plugin
	Version string `yaml:"version"`
}

// Validate is a method to check if the manifest is valid and is compliant with
// the requirements.
func (m *Manifest) Validate() error {
	errs := []error{}
	if m.Name == "" {
		errs = append(errs, fmt.Errorf("Name is required"))
	}
	if m.Author == "" {
		errs = append(errs, fmt.Errorf("Author is required"))
	}
	if m.Version == "" {
		errs = append(errs, fmt.Errorf("Version is required"))
	}
	// regex from: https://stackoverflow.com/a/68921827
	validVersion, err := regexp.MatchString(`^v([1-9]\d*|0)(\.(([1-9]\d*)|0)){2}$`,
		m.Version)
	if err != nil {
		errs = append(errs, err)
	}
	if !validVersion {
		errs = append(errs, fmt.Errorf("Version has to be of form: vX.X.X"))
	}
	if len(errs) != 0 {
		return customerrs.CombinedErrListError{Errors: errs}
	}
	return nil
}

// ReadManifestFromFile reads a manifest file and returns a pointer to a newly
// created Manifest.
func ReadManifestFromFile(path string) (*Manifest, error) {
	manifest := &Manifest{}

	manifestFile, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	err = yaml.Unmarshal(manifestFile, manifest)
	if err != nil {
		return nil, err
	}

	// validate the loaded manifest
	if err := manifest.Validate(); err != nil {
		return nil, err
	}

	return manifest, nil
}