// src/config.go
package main

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

	"gopkg.in/yaml.v3"
)

// LabelerRule represents a rule mapping file patterns to a label area.
type LabelerRule struct {
	ChangedFiles []ChangedFile `yaml:"changed-files"`
}

// ChangedFile represents a single file pattern rule.
type ChangedFile struct {
	AnyGlobToAnyFile string `yaml:"any-glob-to-any-file"`
}

// LabelerConfig represents the entire labeler configuration.
type LabelerConfig map[string][]LabelerRule

// LoadLabelerConfig loads and parses the labeler.yaml file.
func LoadLabelerConfig(path string) (LabelerConfig, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read labeler config file: %w", err)
	}

	var config LabelerConfig
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, fmt.Errorf("failed to parse labeler config file: %w", err)
	}

	return config, nil
}

// LoadLabelsConfig loads and parses the labels.yaml file.
func LoadLabelsConfig(path string) ([]ForgejoLabel, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read labels config file: %w", err)
	}

	var labels []ForgejoLabel
	if err := yaml.Unmarshal(data, &labels); err != nil {
		return nil, fmt.Errorf("failed to parse labels config file: %w", err)
	}

	return labels, nil
}

// GetLabelPatterns returns a map of label areas to their corresponding file patterns.
func (lc LabelerConfig) GetLabelPatterns() map[string][]string {
	patterns := make(map[string][]string)
	for area, rules := range lc {
		for _, rule := range rules {
			for _, cf := range rule.ChangedFiles {
				patterns[area] = append(patterns[area], cf.AnyGlobToAnyFile)
			}
		}
	}
	return patterns
}

// DetermineLabels determines which labels should be applied based on changed files.
// `changedFiles` is a list of file paths that have been changed.
func DetermineLabels(labelerConfig LabelerConfig, changedFiles []string) ([]string, error) {
	patterns := labelerConfig.GetLabelPatterns()
	labelSet := make(map[string]struct{})

	for area, globs := range patterns {
		for _, glob := range globs {
			for _, file := range changedFiles {
				matched, err := filepath.Match(glob, file)
				if err != nil {
					return nil, fmt.Errorf("invalid glob pattern '%s': %w", glob, err)
				}
				if matched {
					labelSet[area] = struct{}{}
					break // No need to check more files for this pattern
				}
			}
		}
	}

	var labels []string
	for label := range labelSet {
		labels = append(labels, label)
	}

	return labels, nil
}

// ValidateLabelerConfig validates the labeler configuration.
func ValidateLabelerConfig(config LabelerConfig) error {
	for area, rules := range config {
		if area == "" {
			return fmt.Errorf("labeler config contains an empty area")
		}
		for _, rule := range rules {
			for _, cf := range rule.ChangedFiles {
				if cf.AnyGlobToAnyFile == "" {
					return fmt.Errorf("labeler config for area '%s' contains an empty file pattern", area)
				}
			}
		}
	}
	return nil
}

// ValidateLabelsConfig validates the labels configuration.
func ValidateLabelsConfig(labels []ForgejoLabel) error {
	labelNames := make(map[string]struct{})
	for _, label := range labels {
		if label.Name == "" {
			return fmt.Errorf("labels config contains a label with an empty name")
		}
		if label.Color == "" {
			return fmt.Errorf("labels config contains a label with an empty color")
		}
		// Optionally, validate color format
		if !isValidHexColor(label.Color) {
			return fmt.Errorf("label '%s' has an invalid color format: %s", label.Name, label.Color)
		}
		// Check for duplicate labels
		if _, exists := labelNames[label.Name]; exists {
			return fmt.Errorf("duplicate label name found: %s", label.Name)
		}
		labelNames[label.Name] = struct{}{}
	}
	return nil
}

func isValidHexColor(color string) bool {
	match, _ := regexp.MatchString("^([A-Fa-f0-9]{6})$", color)
	return match
}
