package internal

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/yaml.v3"
)

const base = ".custom-gcl"

const defaultBinaryName = "custom-gcl"

// Configuration represents the configuration file.
type Configuration struct {
	// golangci-lint version.
	Version string `yaml:"version"`

	// Name of the binary.
	Name string `yaml:"name,omitempty"`

	// Destination is the path to a directory to store the binary.
	Destination string `yaml:"destination,omitempty"`

	// Plugins information.
	Plugins []*Plugin `yaml:"plugins,omitempty"`
}

// Validate checks and clean the configuration.
func (c *Configuration) Validate() error {
	if strings.TrimSpace(c.Version) == "" {
		return errors.New("root field 'version' is required")
	}

	if strings.TrimSpace(c.Name) == "" {
		c.Name = defaultBinaryName
	}

	if len(c.Plugins) == 0 {
		return errors.New("no plugins defined")
	}

	for _, plugin := range c.Plugins {
		if strings.TrimSpace(plugin.Module) == "" {
			return errors.New("field 'module' is required")
		}

		if strings.TrimSpace(plugin.Import) == "" {
			plugin.Import = plugin.Module
		}

		if strings.TrimSpace(plugin.Path) == "" && strings.TrimSpace(plugin.Version) == "" {
			return errors.New("missing information: 'version' or 'path' should be provided")
		}

		if strings.TrimSpace(plugin.Path) != "" && strings.TrimSpace(plugin.Version) != "" {
			return errors.New("invalid configuration: 'version' and 'path' should not be provided at the same time")
		}

		if strings.TrimSpace(plugin.Path) == "" {
			continue
		}

		abs, err := filepath.Abs(plugin.Path)
		if err != nil {
			return err
		}

		plugin.Path = abs
	}

	return nil
}

// Plugin represents information about a plugin.
type Plugin struct {
	// Module name.
	Module string `yaml:"module"`

	// Import to use.
	Import string `yaml:"import,omitempty"`

	// Version of the module.
	// Only for module available through a Go proxy.
	Version string `yaml:"version,omitempty"`

	// Path to the local module.
	// Only for local module.
	Path string `yaml:"path,omitempty"`
}

func LoadConfiguration() (*Configuration, error) {
	configFilePath, err := findConfigurationFile()
	if err != nil {
		return nil, fmt.Errorf("file %s not found: %w", configFilePath, err)
	}

	file, err := os.Open(configFilePath)
	if err != nil {
		return nil, fmt.Errorf("file %s open: %w", configFilePath, err)
	}

	defer func() { _ = file.Close() }()

	var cfg Configuration

	err = yaml.NewDecoder(file).Decode(&cfg)
	if err != nil {
		return nil, fmt.Errorf("YAML decoding: %w", err)
	}

	return &cfg, nil
}

func findConfigurationFile() (string, error) {
	entries, err := os.ReadDir(".")
	if err != nil {
		return "", fmt.Errorf("read directory: %w", err)
	}

	for _, entry := range entries {
		ext := filepath.Ext(entry.Name())

		switch strings.ToLower(strings.TrimPrefix(ext, ".")) {
		case "yml", "yaml", "json":
			if isConf(ext, entry.Name()) {
				return entry.Name(), nil
			}
		}
	}

	return "", errors.New("configuration file not found")
}

func isConf(ext, name string) bool {
	return base+ext == name
}