package gitlabcivalidator

import (
	"fmt"
	"gopkg.in/yaml.v3"
	"tea.gitpark.ru/Azaki/CI_VALIDATOR/errors"
)

// ParseGitLabCIConfig parses the YAML and returns the GitLabCIConfig structure.
func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConfig, error) {
	var config GitLabCIConfig
	var root yaml.Node

	// Parse YAML into the root node.
	err := yaml.Unmarshal(data, &root)
	if err != nil {
		return nil, fmt.Errorf("YAML syntax error: %w", err)
	}
	if len(root.Content) == 0 {
		return nil, fmt.Errorf(".gitlab-ci.yml file is empty or does not contain valid data")
	}
	if root.Kind != yaml.DocumentNode || len(root.Content) == 0 {
		return nil, fmt.Errorf("invalid YAML format: expected a root object")
	}
	mainNode := root.Content[0]

	// Decode known top-level keys into config.
	err = mainNode.Decode(&config)
	if err != nil {
		return nil, fmt.Errorf("failed to decode GitLab CI configuration: %w", err)
	}

	// Allowed top-level keys that are NOT jobs.
	allowedKeys := map[string]struct{}{
		"default":       {},
		"include":       {},
		"before_script": {},
		"image":         {},
		"services":      {},
		"after_script":  {},
		"variables":     {},
		"stages":        {},
		"cache":         {},
		"workflow":      {},
		"changes":       {},
		"paths":         {},
	}

	// Initialize config.Jobs if not already set.
	if config.Jobs == nil {
		config.Jobs = make(map[string]*Job)
	}

	// Now, iterate over the top-level mapping keys.
	if mainNode.Kind == yaml.MappingNode {
		for i := 0; i < len(mainNode.Content)-1; i += 2 {
			keyNode := mainNode.Content[i]
			valNode := mainNode.Content[i+1]

			if keyNode.Value == "variables" {
				config.VariablesNode = valNode
			}
			// If the key is not one of the allowed top-level keys, treat it as a job.
			if _, ok := allowedKeys[keyNode.Value]; !ok {
				// Create a new Job.
				job := &Job{
					Name:            keyNode.Value,
					Line:            keyNode.Line,
					Column:          keyNode.Column,
					DependencyLines: make(map[string]struct{ Line, Column int }),
				}
				// Even if the job value is not a mapping node, add the job to config for further validation.
				if valNode.Kind == yaml.MappingNode {
					// Optionally, you can validate unknown keys inside the job mapping here.
					// For example:
					validateJobMapping(valNode, ve)
					// Decode the job mapping.
					err = valNode.Decode(job)
					if err != nil {
						return nil, fmt.Errorf("failed to decode job '%s': %w", job.Name, err)
					}
					// Process inner fields manually (e.g. dependencies, needs, only, etc.)
					for d := 0; d < len(valNode.Content)-1; d += 2 {
						depKey := valNode.Content[d]
						depVal := valNode.Content[d+1]
						switch depKey.Value {
						case "dependencies":
							if depVal.Kind == yaml.SequenceNode {
								for k := 0; k < len(depVal.Content); k++ {
									depNode := depVal.Content[k]
									job.Dependencies = append(job.Dependencies, depNode.Value)
									job.DependencyLines[depNode.Value] = struct {
										Line   int
										Column int
									}{depNode.Line, depNode.Column}
								}
							}
						case "needs":
							if depVal.Kind == yaml.SequenceNode {
								job.NeedLines = make(map[string]struct {
									Line   int
									Column int
								})
								for k := 0; k < len(depVal.Content); k++ {
									needNode := depVal.Content[k]
									job.Needs = append(job.Needs, needNode.Value)
									job.NeedLines[needNode.Value] = struct {
										Line   int
										Column int
									}{needNode.Line, needNode.Column}
								}
							}
						case "only":
							if depVal.Kind == yaml.ScalarNode {
								job.Only = depVal.Value
							} else if depVal.Kind == yaml.SequenceNode {
								var arr []string
								for k := 0; k < len(depVal.Content); k++ {
									arr = append(arr, depVal.Content[k].Value)
								}
								job.Only = arr
							}
							job.OnlyLine = depKey.Line
							job.OnlyColumn = depKey.Column
						case "variables":
							// Заполняем поля переменных для job.
							job.VariablesNode = depVal
							var vars map[string]interface{}
							if err := depVal.Decode(&vars); err != nil {
								return nil, fmt.Errorf("failed to decode variables for job '%s': %w", job.Name, err)
							}
							job.Variables = vars
						}
					}
				} else {
					// If the job value is not a mapping, leave job fields as default.
				}
				// Save the job in config.
				config.Jobs[job.Name] = job
			}
		}
	}

	return &config, nil
}

// findUnknownRootKeysWithSpan scans the mainNode for unknown keys and returns their information.
func findUnknownRootKeysWithSpan(mainNode *yaml.Node) []errors.UnknownKeyError {
	allowedKeys := map[string]struct{}{
		"default": {},
		"include": {},
		//"before_script": {},
		"image":        {},
		"services":     {},
		"after_script": {},
		"variables":    {},
		"stages":       {},
		"cache":        {},
		"workflow":     {},
		"jobs":         {},
		"changes":      {},
		"paths":        {},
	}

	var unknowns []errors.UnknownKeyError
	if mainNode.Kind != yaml.MappingNode {
		return unknowns
	}

	for i := 0; i < len(mainNode.Content)-1; i += 2 {
		keyNode := mainNode.Content[i]
		if _, ok := allowedKeys[keyNode.Value]; !ok && keyNode.Value == "before_script" {
			endLine, endCol := keyNode.Line, keyNode.Column+len(keyNode.Value)
			unknowns = append(unknowns, errors.UnknownKeyError{
				Key:       keyNode.Value,
				Line:      keyNode.Line,
				Column:    keyNode.Column,
				EndLine:   endLine,
				EndColumn: endCol,
			})
		}
	}
	return unknowns
}