package maven

import (
	"encoding/xml"
	"fmt"
	"github.com/SAP/jenkins-library/pkg/piperutils"
	"path/filepath"
)

// Project describes the Maven object model.
type Project struct {
	XMLName      xml.Name     `xml:"project"`
	Parent       Parent       `xml:"parent"`
	GroupID      string       `xml:"groupId"`
	ArtifactID   string       `xml:"artifactId"`
	Version      string       `xml:"version"`
	Packaging    string       `xml:"packaging"`
	Name         string       `xml:"name"`
	Dependencies []Dependency `xml:"dependencies>dependency"`
	Modules      []string     `xml:"modules>module"`
}

// Parent describes the coordinates a module's parent POM.
type Parent struct {
	XMLName    xml.Name `xml:"parent"`
	GroupID    string   `xml:"groupId"`
	ArtifactID string   `xml:"artifactId"`
	Version    string   `xml:"version"`
}

// Dependency describes a dependency of the module.
type Dependency struct {
	XMLName    xml.Name    `xml:"dependency"`
	GroupID    string      `xml:"groupId"`
	ArtifactID string      `xml:"artifactId"`
	Version    string      `xml:"version"`
	Classifier string      `xml:"classifier"`
	Type       string      `xml:"type"`
	Scope      string      `xml:"scope"`
	Exclusions []Exclusion `xml:"exclusions>exclusion"`
}

// Exclusion describes an exclusion within a dependency.
type Exclusion struct {
	XMLName    xml.Name `xml:"exclusion"`
	GroupID    string   `xml:"groupId"`
	ArtifactID string   `xml:"artifactId"`
}

// ParsePOM parses the provided XML raw data into a Project.
func ParsePOM(xmlData []byte) (*Project, error) {
	project := Project{}
	err := xml.Unmarshal(xmlData, &project)
	if err != nil {
		return nil, fmt.Errorf("failed to parse POM data: %w", err)
	}
	return &project, nil
}

// ModuleInfo describes a location and Project of a maven module.
type ModuleInfo struct {
	PomXMLPath string
	Project    *Project
}

type visitUtils interface {
	FileExists(path string) (bool, error)
	FileRead(path string) ([]byte, error)
}

// VisitAllMavenModules ...
func VisitAllMavenModules(path string, utils visitUtils, excludes []string, callback func(info ModuleInfo) error) error {
	pomXMLPath := filepath.Join(path, "pom.xml")
	if piperutils.ContainsString(excludes, pomXMLPath) {
		return nil
	}

	exists, _ := utils.FileExists(pomXMLPath)
	if !exists {
		return nil
	}

	pomXMLContents, err := utils.FileRead(pomXMLPath)
	if err != nil {
		return fmt.Errorf("failed to read file contents of '%s': %w", pomXMLPath, err)
	}

	project, err := ParsePOM(pomXMLContents)
	if err != nil {
		return fmt.Errorf("failed to parse file contents of '%s': %w", pomXMLPath, err)
	}

	err = callback(ModuleInfo{PomXMLPath: pomXMLPath, Project: project})
	if err != nil {
		return err
	}

	if len(project.Modules) == 0 {
		return nil
	}

	for _, module := range project.Modules {
		subPomPath := filepath.Join(path, module)
		err = VisitAllMavenModules(subPomPath, utils, excludes, callback)
		if err != nil {
			return err
		}
	}
	return nil
}
