package plugins

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/pkg/errors"
)

// Plugin represents jenkins plugin.
type Plugin struct {
	Name                     string `json:"name"`
	Version                  string `json:"version"`
	DownloadURL              string `json:"downloadURL"`
	rootPluginNameAndVersion string
}

func (p Plugin) String() string {
	return fmt.Sprintf("%s:%s", p.Name, p.Version)
}

var (
	// NamePattern is the plugin name regex pattern
	NamePattern = regexp.MustCompile(`^[0-9a-zA-Z-_]+$`)
	// VersionPattern is the plugin version regex pattern
	VersionPattern = regexp.MustCompile(`^[0-9a-zA-Z+\\.-]+$`)
	// DownloadURLPattern is the plugin download url regex pattern
	DownloadURLPattern = regexp.MustCompile(`https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)`)
)

// New creates plugin from string, for example "name-of-plugin:0.0.1".
func New(nameWithVersion string) (*Plugin, error) {
	val := strings.SplitN(nameWithVersion, ":", 2)
	if val == nil || len(val) != 2 {
		return nil, errors.Errorf("invalid plugin format '%s'", nameWithVersion)
	}
	name := val[0]
	version := val[1]

	if err := validatePlugin(name, version, ""); err != nil {
		return nil, err
	}

	return &Plugin{
		Name:    name,
		Version: version,
	}, nil
}

// NewPlugin creates plugin from name and version, for example "name-of-plugin:0.0.1".
func NewPlugin(name, version, downloadURL string) (*Plugin, error) {
	if err := validatePlugin(name, version, downloadURL); err != nil {
		return nil, err
	}

	return &Plugin{
		Name:        name,
		Version:     version,
		DownloadURL: downloadURL,
	}, nil
}

func validatePlugin(name, version, downloadURL string) error {
	if ok := NamePattern.MatchString(name); !ok {
		return errors.Errorf("invalid plugin name '%s:%s', must follow pattern '%s'", name, version, NamePattern.String())
	}
	if ok := VersionPattern.MatchString(version); !ok {
		return errors.Errorf("invalid plugin version '%s:%s', must follow pattern '%s'", name, version, VersionPattern.String())
	}
	if len(downloadURL) > 0 {
		if ok := DownloadURLPattern.MatchString(downloadURL); !ok {
			return errors.Errorf("invalid download URL '%s' for plugin name %s:%s, must follow pattern '%s'", downloadURL, name, version, DownloadURLPattern.String())
		}
	}

	return nil
}

// Must returns plugin from pointer and throws panic when error is set.
func Must(plugin *Plugin, err error) Plugin {
	if err != nil {
		panic(err)
	}

	return *plugin
}

// VerifyDependencies checks if all plugins have compatible versions.
func VerifyDependencies(values ...map[Plugin][]Plugin) []string {
	var messages []string
	// key - plugin name, value array of versions
	allPlugins := make(map[string][]Plugin)

	for _, value := range values {
		for rootPlugin, plugins := range value {
			allPlugins[rootPlugin.Name] = append(allPlugins[rootPlugin.Name], Plugin{
				Name:                     rootPlugin.Name,
				Version:                  rootPlugin.Version,
				rootPluginNameAndVersion: rootPlugin.String(),
			})
			for _, plugin := range plugins {
				allPlugins[plugin.Name] = append(allPlugins[plugin.Name], Plugin{
					Name:                     plugin.Name,
					Version:                  plugin.Version,
					rootPluginNameAndVersion: rootPlugin.String(),
				})
			}
		}
	}

	for pluginName, versions := range allPlugins {
		if len(versions) == 1 {
			continue
		}

		for _, firstVersion := range versions {
			for _, secondVersion := range versions {
				if firstVersion.Version != secondVersion.Version {
					messages = append(messages, fmt.Sprintf("Plugin '%s' requires version '%s' but plugin '%s' requires '%s' for plugin '%s'",
						firstVersion.rootPluginNameAndVersion,
						firstVersion.Version,
						secondVersion.rootPluginNameAndVersion,
						secondVersion.Version,
						pluginName,
					))
				}
			}
		}
	}

	return messages
}
