package jenkins

import (
	"fmt"
	"github.com/beevik/etree"
	"kubesphere.io/kubesphere/pkg/simple/client/devops"
	"strconv"
	"strings"
	"time"
)

func replaceXmlVersion(config, oldVersion, targetVersion string) string {
	lines := strings.Split(config, "\n")
	lines[0] = strings.Replace(lines[0], oldVersion, targetVersion, -1)
	output := strings.Join(lines, "\n")
	return output
}

func createPipelineConfigXml(pipeline *devops.NoScmPipeline) (string, error) {
	doc := etree.NewDocument()
	xmlString := `<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <actions>
    <org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobAction plugin="pipeline-model-definition"/>
    <org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction plugin="pipeline-model-definition">
      <jobProperties/>
      <triggers/>
      <parameters/>
      <options/>
    </org.jenkinsci.plugins.pipeline.modeldefinition.actions.DeclarativeJobPropertyTrackerAction>
  </actions>
</flow-definition>
`
	doc.ReadFromString(xmlString)
	flow := doc.SelectElement("flow-definition")
	flow.CreateElement("description").SetText(pipeline.Description)
	properties := flow.CreateElement("properties")

	if pipeline.DisableConcurrent {
		properties.CreateElement("org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty")
	}

	if pipeline.Discarder != nil {
		discarder := properties.CreateElement("jenkins.model.BuildDiscarderProperty")
		strategy := discarder.CreateElement("strategy")
		strategy.CreateAttr("class", "hudson.tasks.LogRotator")
		strategy.CreateElement("daysToKeep").SetText(pipeline.Discarder.DaysToKeep)
		strategy.CreateElement("numToKeep").SetText(pipeline.Discarder.NumToKeep)
		strategy.CreateElement("artifactDaysToKeep").SetText("-1")
		strategy.CreateElement("artifactNumToKeep").SetText("-1")
	}
	if pipeline.Parameters != nil {
		appendParametersToEtree(properties, pipeline.Parameters)
	}

	if pipeline.TimerTrigger != nil {
		triggers := properties.
			CreateElement("org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty").
			CreateElement("triggers")
		triggers.CreateElement("hudson.triggers.TimerTrigger").CreateElement("spec").SetText(pipeline.TimerTrigger.Cron)
	}

	pipelineDefine := flow.CreateElement("definition")
	pipelineDefine.CreateAttr("class", "org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition")
	pipelineDefine.CreateAttr("plugin", "workflow-cps")
	pipelineDefine.CreateElement("script").SetText(pipeline.Jenkinsfile)

	pipelineDefine.CreateElement("sandbox").SetText("true")

	flow.CreateElement("triggers")

	if pipeline.RemoteTrigger != nil {
		flow.CreateElement("authToken").SetText(pipeline.RemoteTrigger.Token)
	}
	flow.CreateElement("disabled").SetText("false")

	doc.Indent(2)
	stringXml, err := doc.WriteToString()
	if err != nil {
		return "", err
	}
	return replaceXmlVersion(stringXml, "1.0", "1.1"), err
}

func parsePipelineConfigXml(config string) (*devops.NoScmPipeline, error) {
	pipeline := &devops.NoScmPipeline{}
	config = replaceXmlVersion(config, "1.1", "1.0")
	doc := etree.NewDocument()
	err := doc.ReadFromString(config)
	if err != nil {
		return nil, err
	}
	flow := doc.SelectElement("flow-definition")
	if flow == nil {
		return nil, fmt.Errorf("can not find pipeline definition")
	}
	pipeline.Description = flow.SelectElement("description").Text()

	properties := flow.SelectElement("properties")
	if properties.
		SelectElement(
			"org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty") != nil {
		pipeline.DisableConcurrent = true
	}
	if properties.SelectElement("jenkins.model.BuildDiscarderProperty") != nil {
		strategy := properties.
			SelectElement("jenkins.model.BuildDiscarderProperty").
			SelectElement("strategy")
		pipeline.Discarder = &devops.DiscarderProperty{
			DaysToKeep: strategy.SelectElement("daysToKeep").Text(),
			NumToKeep:  strategy.SelectElement("numToKeep").Text(),
		}
	}
	pipeline.Parameters = &devops.Parameters{}
	pipeline.Parameters = getParametersfromEtree(properties)
	if len(*pipeline.Parameters) == 0 {
		pipeline.Parameters = nil
	}

	if triggerProperty := properties.
		SelectElement(
			"org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty"); triggerProperty != nil {
		triggers := triggerProperty.SelectElement("triggers")
		if timerTrigger := triggers.SelectElement("hudson.triggers.TimerTrigger"); timerTrigger != nil {
			pipeline.TimerTrigger = &devops.TimerTrigger{
				Cron: timerTrigger.SelectElement("spec").Text(),
			}
		}
	}
	if authToken := flow.SelectElement("authToken"); authToken != nil {
		pipeline.RemoteTrigger = &devops.RemoteTrigger{
			Token: authToken.Text(),
		}
	}
	if definition := flow.SelectElement("definition"); definition != nil {
		if script := definition.SelectElement("script"); script != nil {
			pipeline.Jenkinsfile = script.Text()
		}
	}
	return pipeline, nil
}

func appendParametersToEtree(properties *etree.Element, parameters *devops.Parameters) {
	parameterDefinitions := properties.CreateElement("hudson.model.ParametersDefinitionProperty").
		CreateElement("parameterDefinitions")
	for _, parameter := range *parameters {
		for className, typeName := range devops.ParameterTypeMap {
			if typeName == parameter.Type {
				paramDefine := parameterDefinitions.CreateElement(className)
				paramDefine.CreateElement("name").SetText(parameter.Name)
				paramDefine.CreateElement("description").SetText(parameter.Description)
				switch parameter.Type {
				case "choice":
					choices := paramDefine.CreateElement("choices")
					choices.CreateAttr("class", "java.util.Arrays$ArrayList")
					a := choices.CreateElement("a")
					a.CreateAttr("class", "string-array")
					choiceValues := strings.Split(parameter.DefaultValue, "\n")
					for _, choiceValue := range choiceValues {
						a.CreateElement("string").SetText(choiceValue)
					}
				case "file":
					break
				default:
					paramDefine.CreateElement("defaultValue").SetText(parameter.DefaultValue)
				}
			}
		}
	}
}

func getParametersfromEtree(properties *etree.Element) *devops.Parameters {
	var parameters devops.Parameters
	if parametersProperty := properties.SelectElement("hudson.model.ParametersDefinitionProperty"); parametersProperty != nil {
		params := parametersProperty.SelectElement("parameterDefinitions").ChildElements()
		for _, param := range params {
			switch param.Tag {
			case "hudson.model.StringParameterDefinition":
				parameters = append(parameters, &devops.Parameter{
					Name:         param.SelectElement("name").Text(),
					Description:  param.SelectElement("description").Text(),
					DefaultValue: param.SelectElement("defaultValue").Text(),
					Type:         devops.ParameterTypeMap["hudson.model.StringParameterDefinition"],
				})
			case "hudson.model.BooleanParameterDefinition":
				parameters = append(parameters, &devops.Parameter{
					Name:         param.SelectElement("name").Text(),
					Description:  param.SelectElement("description").Text(),
					DefaultValue: param.SelectElement("defaultValue").Text(),
					Type:         devops.ParameterTypeMap["hudson.model.BooleanParameterDefinition"],
				})
			case "hudson.model.TextParameterDefinition":
				parameters = append(parameters, &devops.Parameter{
					Name:         param.SelectElement("name").Text(),
					Description:  param.SelectElement("description").Text(),
					DefaultValue: param.SelectElement("defaultValue").Text(),
					Type:         devops.ParameterTypeMap["hudson.model.TextParameterDefinition"],
				})
			case "hudson.model.FileParameterDefinition":
				parameters = append(parameters, &devops.Parameter{
					Name:        param.SelectElement("name").Text(),
					Description: param.SelectElement("description").Text(),
					Type:        devops.ParameterTypeMap["hudson.model.FileParameterDefinition"],
				})
			case "hudson.model.PasswordParameterDefinition":
				parameters = append(parameters, &devops.Parameter{
					Name:         param.SelectElement("name").Text(),
					Description:  param.SelectElement("description").Text(),
					DefaultValue: param.SelectElement("name").Text(),
					Type:         devops.ParameterTypeMap["hudson.model.PasswordParameterDefinition"],
				})
			case "hudson.model.ChoiceParameterDefinition":
				choiceParameter := &devops.Parameter{
					Name:        param.SelectElement("name").Text(),
					Description: param.SelectElement("description").Text(),
					Type:        devops.ParameterTypeMap["hudson.model.ChoiceParameterDefinition"],
				}
				choices := param.SelectElement("choices").SelectElement("a").SelectElements("string")
				for _, choice := range choices {
					choiceParameter.DefaultValue += fmt.Sprintf("%s\n", choice.Text())
				}
				choiceParameter.DefaultValue = strings.TrimSpace(choiceParameter.DefaultValue)
				parameters = append(parameters, choiceParameter)
			default:
				parameters = append(parameters, &devops.Parameter{
					Name:         param.SelectElement("name").Text(),
					Description:  param.SelectElement("description").Text(),
					DefaultValue: "unknown",
					Type:         param.Tag,
				})
			}
		}
	}
	return &parameters
}

func appendGitSourceToEtree(source *etree.Element, gitSource *devops.GitSource) {
	source.CreateAttr("class", "jenkins.plugins.git.GitSCMSource")
	source.CreateAttr("plugin", "git")
	source.CreateElement("id").SetText(gitSource.ScmId)
	source.CreateElement("remote").SetText(gitSource.Url)
	if gitSource.CredentialId != "" {
		source.CreateElement("credentialsId").SetText(gitSource.CredentialId)
	}
	traits := source.CreateElement("traits")
	if gitSource.DiscoverBranches {
		traits.CreateElement("jenkins.plugins.git.traits.BranchDiscoveryTrait")
	}
	if gitSource.CloneOption != nil {
		cloneExtension := traits.CreateElement("jenkins.plugins.git.traits.CloneOptionTrait").CreateElement("extension")
		cloneExtension.CreateAttr("class", "hudson.plugins.git.extensions.impl.CloneOption")
		cloneExtension.CreateElement("shallow").SetText(strconv.FormatBool(gitSource.CloneOption.Shallow))
		cloneExtension.CreateElement("noTags").SetText(strconv.FormatBool(false))
		cloneExtension.CreateElement("honorRefspec").SetText(strconv.FormatBool(true))
		cloneExtension.CreateElement("reference")
		if gitSource.CloneOption.Timeout >= 0 {
			cloneExtension.CreateElement("timeout").SetText(strconv.Itoa(gitSource.CloneOption.Timeout))
		} else {
			cloneExtension.CreateElement("timeout").SetText(strconv.Itoa(10))
		}

		if gitSource.CloneOption.Depth >= 0 {
			cloneExtension.CreateElement("depth").SetText(strconv.Itoa(gitSource.CloneOption.Depth))
		} else {
			cloneExtension.CreateElement("depth").SetText(strconv.Itoa(1))
		}
	}

	if gitSource.RegexFilter != "" {
		regexTraits := traits.CreateElement("jenkins.scm.impl.trait.RegexSCMHeadFilterTrait")
		regexTraits.CreateAttr("plugin", "scm-api@2.4.0")
		regexTraits.CreateElement("regex").SetText(gitSource.RegexFilter)
	}
	return
}

func getGitSourcefromEtree(source *etree.Element) *devops.GitSource {
	var gitSource devops.GitSource
	if credential := source.SelectElement("credentialsId"); credential != nil {
		gitSource.CredentialId = credential.Text()
	}
	if remote := source.SelectElement("remote"); remote != nil {
		gitSource.Url = remote.Text()
	}

	traits := source.SelectElement("traits")
	if branchDiscoverTrait := traits.SelectElement(
		"jenkins.plugins.git.traits.BranchDiscoveryTrait"); branchDiscoverTrait != nil {
		gitSource.DiscoverBranches = true
	}
	if cloneTrait := traits.SelectElement(
		"jenkins.plugins.git.traits.CloneOptionTrait"); cloneTrait != nil {
		if cloneExtension := cloneTrait.SelectElement(
			"extension"); cloneExtension != nil {
			gitSource.CloneOption = &devops.GitCloneOption{}
			if value, err := strconv.ParseBool(cloneExtension.SelectElement("shallow").Text()); err == nil {
				gitSource.CloneOption.Shallow = value
			}
			if value, err := strconv.ParseInt(cloneExtension.SelectElement("timeout").Text(), 10, 32); err == nil {
				gitSource.CloneOption.Timeout = int(value)
			}
			if value, err := strconv.ParseInt(cloneExtension.SelectElement("depth").Text(), 10, 32); err == nil {
				gitSource.CloneOption.Depth = int(value)
			}
		}
	}
	if regexTrait := traits.SelectElement(
		"jenkins.scm.impl.trait.RegexSCMHeadFilterTrait"); regexTrait != nil {
		if regex := regexTrait.SelectElement("regex"); regex != nil {
			gitSource.RegexFilter = regex.Text()
		}
	}
	return &gitSource
}

func getGithubSourcefromEtree(source *etree.Element) *devops.GithubSource {
	var githubSource devops.GithubSource
	if credential := source.SelectElement("credentialsId"); credential != nil {
		githubSource.CredentialId = credential.Text()
	}
	if repoOwner := source.SelectElement("repoOwner"); repoOwner != nil {
		githubSource.Owner = repoOwner.Text()
	}
	if repository := source.SelectElement("repository"); repository != nil {
		githubSource.Repo = repository.Text()
	}
	if apiUri := source.SelectElement("apiUri"); apiUri != nil {
		githubSource.ApiUri = apiUri.Text()
	}
	traits := source.SelectElement("traits")
	if branchDiscoverTrait := traits.SelectElement(
		"org.jenkinsci.plugins.github__branch__source.BranchDiscoveryTrait"); branchDiscoverTrait != nil {
		strategyId, _ := strconv.Atoi(branchDiscoverTrait.SelectElement("strategyId").Text())
		githubSource.DiscoverBranches = strategyId
	}
	if originPRDiscoverTrait := traits.SelectElement(
		"org.jenkinsci.plugins.github__branch__source.OriginPullRequestDiscoveryTrait"); originPRDiscoverTrait != nil {
		strategyId, _ := strconv.Atoi(originPRDiscoverTrait.SelectElement("strategyId").Text())
		githubSource.DiscoverPRFromOrigin = strategyId
	}
	if forkPRDiscoverTrait := traits.SelectElement(
		"org.jenkinsci.plugins.github__branch__source.ForkPullRequestDiscoveryTrait"); forkPRDiscoverTrait != nil {
		strategyId, _ := strconv.Atoi(forkPRDiscoverTrait.SelectElement("strategyId").Text())
		trustClass := forkPRDiscoverTrait.SelectElement("trust").SelectAttr("class").Value
		trust := strings.Split(trustClass, "$")
		switch trust[1] {
		case "TrustContributors":
			githubSource.DiscoverPRFromForks = &devops.DiscoverPRFromForks{
				Strategy: strategyId,
				Trust:    1,
			}
		case "TrustEveryone":
			githubSource.DiscoverPRFromForks = &devops.DiscoverPRFromForks{
				Strategy: strategyId,
				Trust:    2,
			}
		case "TrustPermission":
			githubSource.DiscoverPRFromForks = &devops.DiscoverPRFromForks{
				Strategy: strategyId,
				Trust:    3,
			}
		case "TrustNobody":
			githubSource.DiscoverPRFromForks = &devops.DiscoverPRFromForks{
				Strategy: strategyId,
				Trust:    4,
			}
		}
		if cloneTrait := traits.SelectElement(
			"jenkins.plugins.git.traits.CloneOptionTrait"); cloneTrait != nil {
			if cloneExtension := cloneTrait.SelectElement(
				"extension"); cloneExtension != nil {
				githubSource.CloneOption = &devops.GitCloneOption{}
				if value, err := strconv.ParseBool(cloneExtension.SelectElement("shallow").Text()); err == nil {
					githubSource.CloneOption.Shallow = value
				}
				if value, err := strconv.ParseInt(cloneExtension.SelectElement("timeout").Text(), 10, 32); err == nil {
					githubSource.CloneOption.Timeout = int(value)
				}
				if value, err := strconv.ParseInt(cloneExtension.SelectElement("depth").Text(), 10, 32); err == nil {
					githubSource.CloneOption.Depth = int(value)
				}
			}
		}

		if regexTrait := traits.SelectElement(
			"jenkins.scm.impl.trait.RegexSCMHeadFilterTrait"); regexTrait != nil {
			if regex := regexTrait.SelectElement("regex"); regex != nil {
				githubSource.RegexFilter = regex.Text()
			}
		}
	}
	return &githubSource
}

func appendGithubSourceToEtree(source *etree.Element, githubSource *devops.GithubSource) {
	source.CreateAttr("class", "org.jenkinsci.plugins.github_branch_source.GitHubSCMSource")
	source.CreateAttr("plugin", "github-branch-source")
	source.CreateElement("id").SetText(githubSource.ScmId)
	source.CreateElement("credentialsId").SetText(githubSource.CredentialId)
	source.CreateElement("repoOwner").SetText(githubSource.Owner)
	source.CreateElement("repository").SetText(githubSource.Repo)
	if githubSource.ApiUri != "" {
		source.CreateElement("apiUri").SetText(githubSource.ApiUri)
	}
	traits := source.CreateElement("traits")
	if githubSource.DiscoverBranches != 0 {
		traits.CreateElement("org.jenkinsci.plugins.github__branch__source.BranchDiscoveryTrait").
			CreateElement("strategyId").SetText(strconv.Itoa(githubSource.DiscoverBranches))
	}
	if githubSource.DiscoverPRFromOrigin != 0 {
		traits.CreateElement("org.jenkinsci.plugins.github__branch__source.OriginPullRequestDiscoveryTrait").
			CreateElement("strategyId").SetText(strconv.Itoa(githubSource.DiscoverPRFromOrigin))
	}
	if githubSource.DiscoverPRFromForks != nil {
		forkTrait := traits.CreateElement("org.jenkinsci.plugins.github__branch__source.ForkPullRequestDiscoveryTrait")
		forkTrait.CreateElement("strategyId").SetText(strconv.Itoa(githubSource.DiscoverPRFromForks.Strategy))
		trustClass := "org.jenkinsci.plugins.github_branch_source.ForkPullRequestDiscoveryTrait$"
		switch githubSource.DiscoverPRFromForks.Trust {
		case 1:
			trustClass += "TrustContributors"
		case 2:
			trustClass += "TrustEveryone"
		case 3:
			trustClass += "TrustPermission"
		case 4:
			trustClass += "TrustNobody"
		default:
		}
		forkTrait.CreateElement("trust").CreateAttr("class", trustClass)
	}
	if githubSource.CloneOption != nil {
		cloneExtension := traits.CreateElement("jenkins.plugins.git.traits.CloneOptionTrait").CreateElement("extension")
		cloneExtension.CreateAttr("class", "hudson.plugins.git.extensions.impl.CloneOption")
		cloneExtension.CreateElement("shallow").SetText(strconv.FormatBool(githubSource.CloneOption.Shallow))
		cloneExtension.CreateElement("noTags").SetText(strconv.FormatBool(false))
		cloneExtension.CreateElement("honorRefspec").SetText(strconv.FormatBool(true))
		cloneExtension.CreateElement("reference")
		if githubSource.CloneOption.Timeout >= 0 {
			cloneExtension.CreateElement("timeout").SetText(strconv.Itoa(githubSource.CloneOption.Timeout))
		} else {
			cloneExtension.CreateElement("timeout").SetText(strconv.Itoa(10))
		}

		if githubSource.CloneOption.Depth >= 0 {
			cloneExtension.CreateElement("depth").SetText(strconv.Itoa(githubSource.CloneOption.Depth))
		} else {
			cloneExtension.CreateElement("depth").SetText(strconv.Itoa(1))
		}
	}
	if githubSource.RegexFilter != "" {
		regexTraits := traits.CreateElement("jenkins.scm.impl.trait.RegexSCMHeadFilterTrait")
		regexTraits.CreateAttr("plugin", "scm-api@2.4.0")
		regexTraits.CreateElement("regex").SetText(githubSource.RegexFilter)
	}
	return
}

func getBitbucketServerSourceFromEtree(source *etree.Element) *devops.BitbucketServerSource {
	var s devops.BitbucketServerSource
	if credential := source.SelectElement("credentialsId"); credential != nil {
		s.CredentialId = credential.Text()
	}
	if repoOwner := source.SelectElement("repoOwner"); repoOwner != nil {
		s.Owner = repoOwner.Text()
	}
	if repository := source.SelectElement("repository"); repository != nil {
		s.Repo = repository.Text()
	}
	if apiUri := source.SelectElement("serverUrl"); apiUri != nil {
		s.ApiUri = apiUri.Text()
	}
	traits := source.SelectElement("traits")
	if branchDiscoverTrait := traits.SelectElement(
		"com.cloudbees.jenkins.plugins.bitbucket.BranchDiscoveryTrait"); branchDiscoverTrait != nil {
		strategyId, _ := strconv.Atoi(branchDiscoverTrait.SelectElement("strategyId").Text())
		s.DiscoverBranches = strategyId
	}
	if originPRDiscoverTrait := traits.SelectElement(
		"com.cloudbees.jenkins.plugins.bitbucket.OriginPullRequestDiscoveryTrait"); originPRDiscoverTrait != nil {
		strategyId, _ := strconv.Atoi(originPRDiscoverTrait.SelectElement("strategyId").Text())
		s.DiscoverPRFromOrigin = strategyId
	}
	if forkPRDiscoverTrait := traits.SelectElement(
		"com.cloudbees.jenkins.plugins.bitbucket.ForkPullRequestDiscoveryTrait"); forkPRDiscoverTrait != nil {
		strategyId, _ := strconv.Atoi(forkPRDiscoverTrait.SelectElement("strategyId").Text())
		trustClass := forkPRDiscoverTrait.SelectElement("trust").SelectAttr("class").Value
		trust := strings.Split(trustClass, "$")
		switch trust[1] {
		case "TrustEveryone":
			s.DiscoverPRFromForks = &devops.DiscoverPRFromForks{
				Strategy: strategyId,
				Trust:    1,
			}
		case "TrustTeamForks":
			s.DiscoverPRFromForks = &devops.DiscoverPRFromForks{
				Strategy: strategyId,
				Trust:    2,
			}
		case "TrustNobody":
			s.DiscoverPRFromForks = &devops.DiscoverPRFromForks{
				Strategy: strategyId,
				Trust:    3,
			}
		}
		if cloneTrait := traits.SelectElement(
			"jenkins.plugins.git.traits.CloneOptionTrait"); cloneTrait != nil {
			if cloneExtension := cloneTrait.SelectElement(
				"extension"); cloneExtension != nil {
				s.CloneOption = &devops.GitCloneOption{}
				if value, err := strconv.ParseBool(cloneExtension.SelectElement("shallow").Text()); err == nil {
					s.CloneOption.Shallow = value
				}
				if value, err := strconv.ParseInt(cloneExtension.SelectElement("timeout").Text(), 10, 32); err == nil {
					s.CloneOption.Timeout = int(value)
				}
				if value, err := strconv.ParseInt(cloneExtension.SelectElement("depth").Text(), 10, 32); err == nil {
					s.CloneOption.Depth = int(value)
				}
			}
		}

		if regexTrait := traits.SelectElement(
			"jenkins.scm.impl.trait.RegexSCMHeadFilterTrait"); regexTrait != nil {
			if regex := regexTrait.SelectElement("regex"); regex != nil {
				s.RegexFilter = regex.Text()
			}
		}
	}
	return &s
}

func appendBitbucketServerSourceToEtree(source *etree.Element, s *devops.BitbucketServerSource) {
	source.CreateAttr("class", "com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource")
	source.CreateAttr("plugin", "cloudbees-bitbucket-branch-source")
	source.CreateElement("id").SetText(s.ScmId)
	source.CreateElement("credentialsId").SetText(s.CredentialId)
	source.CreateElement("repoOwner").SetText(s.Owner)
	source.CreateElement("repository").SetText(s.Repo)
	source.CreateElement("serverUrl").SetText(s.ApiUri)

	traits := source.CreateElement("traits")
	if s.DiscoverBranches != 0 {
		traits.CreateElement("com.cloudbees.jenkins.plugins.bitbucket.BranchDiscoveryTrait>").
			CreateElement("strategyId").SetText(strconv.Itoa(s.DiscoverBranches))
	}
	if s.DiscoverPRFromOrigin != 0 {
		traits.CreateElement("com.cloudbees.jenkins.plugins.bitbucket.OriginPullRequestDiscoveryTrait").
			CreateElement("strategyId").SetText(strconv.Itoa(s.DiscoverPRFromOrigin))
	}
	if s.DiscoverPRFromForks != nil {
		forkTrait := traits.CreateElement("com.cloudbees.jenkins.plugins.bitbucket.ForkPullRequestDiscoveryTrait")
		forkTrait.CreateElement("strategyId").SetText(strconv.Itoa(s.DiscoverPRFromForks.Strategy))
		trustClass := "com.cloudbees.jenkins.plugins.bitbucket.ForkPullRequestDiscoveryTrait$"
		switch s.DiscoverPRFromForks.Trust {
		case 1:
			trustClass += "TrustEveryone"
		case 2:
			trustClass += "TrustTeamForks"
		case 3:
			trustClass += "TrustNobody"
		default:
			trustClass += "TrustEveryone"
		}
		forkTrait.CreateElement("trust").CreateAttr("class", trustClass)
	}
	if s.CloneOption != nil {
		cloneExtension := traits.CreateElement("jenkins.plugins.git.traits.CloneOptionTrait").CreateElement("extension")
		cloneExtension.CreateAttr("class", "hudson.plugins.git.extensions.impl.CloneOption")
		cloneExtension.CreateElement("shallow").SetText(strconv.FormatBool(s.CloneOption.Shallow))
		cloneExtension.CreateElement("noTags").SetText(strconv.FormatBool(false))
		cloneExtension.CreateElement("honorRefspec").SetText(strconv.FormatBool(true))
		cloneExtension.CreateElement("reference")
		if s.CloneOption.Timeout >= 0 {
			cloneExtension.CreateElement("timeout").SetText(strconv.Itoa(s.CloneOption.Timeout))
		} else {
			cloneExtension.CreateElement("timeout").SetText(strconv.Itoa(10))
		}

		if s.CloneOption.Depth >= 0 {
			cloneExtension.CreateElement("depth").SetText(strconv.Itoa(s.CloneOption.Depth))
		} else {
			cloneExtension.CreateElement("depth").SetText(strconv.Itoa(1))
		}
	}
	if s.RegexFilter != "" {
		regexTraits := traits.CreateElement("jenkins.scm.impl.trait.RegexSCMHeadFilterTrait")
		regexTraits.CreateAttr("plugin", "scm-api@2.4.0")
		regexTraits.CreateElement("regex").SetText(s.RegexFilter)
	}
	return
}

func getSvnSourcefromEtree(source *etree.Element) *devops.SvnSource {
	var s devops.SvnSource
	if remote := source.SelectElement("remoteBase"); remote != nil {
		s.Remote = remote.Text()
	}

	if credentialsId := source.SelectElement("credentialsId"); credentialsId != nil {
		s.CredentialId = credentialsId.Text()
	}

	if includes := source.SelectElement("includes"); includes != nil {
		s.Includes = includes.Text()
	}

	if excludes := source.SelectElement("excludes"); excludes != nil {
		s.Excludes = excludes.Text()
	}
	return &s
}

func appendSvnSourceToEtree(source *etree.Element, s *devops.SvnSource) {
	source.CreateAttr("class", "jenkins.scm.impl.subversion.SubversionSCMSource")
	source.CreateAttr("plugin", "subversion")
	source.CreateElement("id").SetText(s.ScmId)
	if s.CredentialId != "" {
		source.CreateElement("credentialsId").SetText(s.CredentialId)
	}
	if s.Remote != "" {
		source.CreateElement("remoteBase").SetText(s.Remote)
	}
	if s.Includes != "" {
		source.CreateElement("includes").SetText(s.Includes)
	}
	if s.Excludes != "" {
		source.CreateElement("excludes").SetText(s.Excludes)
	}
	return
}

func getSingleSvnSourceFromEtree(source *etree.Element) *devops.SingleSvnSource {
	var s devops.SingleSvnSource
	if scm := source.SelectElement("scm"); scm != nil {
		if locations := scm.SelectElement("locations"); locations != nil {
			if moduleLocations := locations.SelectElement("hudson.scm.SubversionSCM_-ModuleLocation"); moduleLocations != nil {
				if remote := moduleLocations.SelectElement("remote"); remote != nil {
					s.Remote = remote.Text()
				}
				if credentialId := moduleLocations.SelectElement("credentialsId"); credentialId != nil {
					s.CredentialId = credentialId.Text()
				}
			}
		}
	}
	return &s
}

func appendSingleSvnSourceToEtree(source *etree.Element, s *devops.SingleSvnSource) {

	source.CreateAttr("class", "jenkins.scm.impl.SingleSCMSource")
	source.CreateAttr("plugin", "scm-api")
	source.CreateElement("id").SetText(s.ScmId)
	source.CreateElement("name").SetText("master")

	scm := source.CreateElement("scm")
	scm.CreateAttr("class", "hudson.scm.SubversionSCM")
	scm.CreateAttr("plugin", "subversion")

	location := scm.CreateElement("locations").CreateElement("hudson.scm.SubversionSCM_-ModuleLocation")
	if s.Remote != "" {
		location.CreateElement("remote").SetText(s.Remote)
	}
	if s.CredentialId != "" {
		location.CreateElement("credentialsId").SetText(s.CredentialId)
	}
	location.CreateElement("local").SetText(".")
	location.CreateElement("depthOption").SetText("infinity")
	location.CreateElement("ignoreExternalsOption").SetText("true")
	location.CreateElement("cancelProcessOnExternalsFail").SetText("true")

	source.CreateElement("excludedRegions")
	source.CreateElement("includedRegions")
	source.CreateElement("excludedUsers")
	source.CreateElement("excludedRevprop")
	source.CreateElement("excludedCommitMessages")
	source.CreateElement("workspaceUpdater").CreateAttr("class", "hudson.scm.subversion.UpdateUpdater")
	source.CreateElement("ignoreDirPropChanges").SetText("false")
	source.CreateElement("filterChangelog").SetText("false")
	source.CreateElement("quietOperation").SetText("true")

	return
}

func appendMultiBranchJobTriggerToEtree(properties *etree.Element, s *devops.MultiBranchJobTrigger) {
	triggerProperty := properties.CreateElement("org.jenkinsci.plugins.workflow.multibranch.PipelineTriggerProperty")
	triggerProperty.CreateAttr("plugin", "multibranch-action-triggers")
	triggerProperty.CreateElement("createActionJobsToTrigger").SetText(s.CreateActionJobsToTrigger)
	triggerProperty.CreateElement("deleteActionJobsToTrigger").SetText(s.DeleteActionJobsToTrigger)
	return
}

func getMultiBranchJobTriggerfromEtree(properties *etree.Element) *devops.MultiBranchJobTrigger {
	var s devops.MultiBranchJobTrigger
	triggerProperty := properties.SelectElement("org.jenkinsci.plugins.workflow.multibranch.PipelineTriggerProperty")
	if triggerProperty != nil {
		s.CreateActionJobsToTrigger = triggerProperty.SelectElement("createActionJobsToTrigger").Text()
		s.DeleteActionJobsToTrigger = triggerProperty.SelectElement("deleteActionJobsToTrigger").Text()
	}
	return &s
}
func createMultiBranchPipelineConfigXml(projectName string, pipeline *devops.MultiBranchPipeline) (string, error) {
	doc := etree.NewDocument()
	xmlString := `
<?xml version='1.0' encoding='UTF-8'?>
<org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject plugin="workflow-multibranch">
  <actions/>
  <properties>
    <org.jenkinsci.plugins.pipeline.modeldefinition.config.FolderConfig plugin="pipeline-model-definition">
      <dockerLabel></dockerLabel>
      <registry plugin="docker-commons"/>
    </org.jenkinsci.plugins.pipeline.modeldefinition.config.FolderConfig>
  </properties>
  <folderViews class="jenkins.branch.MultiBranchProjectViewHolder" plugin="branch-api">
    <owner class="org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject" reference="../.."/>
  </folderViews>
  <healthMetrics>
    <com.cloudbees.hudson.plugins.folder.health.WorstChildHealthMetric plugin="cloudbees-folder">
      <nonRecursive>false</nonRecursive>
    </com.cloudbees.hudson.plugins.folder.health.WorstChildHealthMetric>
  </healthMetrics>
  <icon class="jenkins.branch.MetadataActionFolderIcon" plugin="branch-api">
    <owner class="org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject" reference="../.."/>
  </icon>
</org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject>`
	err := doc.ReadFromString(xmlString)
	if err != nil {
		return "", err
	}

	project := doc.SelectElement("org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject")
	project.CreateElement("description").SetText(pipeline.Description)

	if pipeline.MultiBranchJobTrigger != nil {
		properties := project.SelectElement("properties")
		appendMultiBranchJobTriggerToEtree(properties, pipeline.MultiBranchJobTrigger)
	}

	if pipeline.Discarder != nil {
		discarder := project.CreateElement("orphanedItemStrategy")
		discarder.CreateAttr("class", "com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy")
		discarder.CreateAttr("plugin", "cloudbees-folder")
		discarder.CreateElement("pruneDeadBranches").SetText("true")
		discarder.CreateElement("daysToKeep").SetText(pipeline.Discarder.DaysToKeep)
		discarder.CreateElement("numToKeep").SetText(pipeline.Discarder.NumToKeep)
	}

	triggers := project.CreateElement("triggers")
	if pipeline.TimerTrigger != nil {
		timeTrigger := triggers.CreateElement(
			"com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger")
		timeTrigger.CreateAttr("plugin", "cloudbees-folder")
		millis, err := strconv.ParseInt(pipeline.TimerTrigger.Interval, 10, 64)
		if err != nil {
			return "", err
		}
		timeTrigger.CreateElement("spec").SetText(toCrontab(millis))
		timeTrigger.CreateElement("interval").SetText(pipeline.TimerTrigger.Interval)

		triggers.CreateElement("disabled").SetText("false")
	}

	sources := project.CreateElement("sources")
	sources.CreateAttr("class", "jenkins.branch.MultiBranchProject$BranchSourceList")
	sources.CreateAttr("plugin", "branch-api")
	sourcesOwner := sources.CreateElement("owner")
	sourcesOwner.CreateAttr("class", "org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject")
	sourcesOwner.CreateAttr("reference", "../..")

	branchSource := sources.CreateElement("data").CreateElement("jenkins.branch.BranchSource")
	branchSourceStrategy := branchSource.CreateElement("strategy")
	branchSourceStrategy.CreateAttr("class", "jenkins.branch.NamedExceptionsBranchPropertyStrategy")
	branchSourceStrategy.CreateElement("defaultProperties").CreateAttr("class", "empty-list")
	branchSourceStrategy.CreateElement("namedExceptions").CreateAttr("class", "empty-list")
	source := branchSource.CreateElement("source")

	switch pipeline.SourceType {
	case "git":
		appendGitSourceToEtree(source, pipeline.GitSource)
	case "github":
		appendGithubSourceToEtree(source, pipeline.GitHubSource)
	case "svn":
		appendSvnSourceToEtree(source, pipeline.SvnSource)
	case "single_svn":
		appendSingleSvnSourceToEtree(source, pipeline.SingleSvnSource)
	case "bitbucket_server":
		appendBitbucketServerSourceToEtree(source, pipeline.BitbucketServerSource)

	default:
		return "", fmt.Errorf("unsupport source type")
	}

	factory := project.CreateElement("factory")
	factory.CreateAttr("class", "org.jenkinsci.plugins.workflow.multibranch.WorkflowBranchProjectFactory")
	factoryOwner := factory.CreateElement("owner")
	factoryOwner.CreateAttr("class", "org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject")
	factoryOwner.CreateAttr("reference", "../..")
	factory.CreateElement("scriptPath").SetText(pipeline.ScriptPath)

	doc.Indent(2)
	stringXml, err := doc.WriteToString()
	return replaceXmlVersion(stringXml, "1.0", "1.1"), err
}

func parseMultiBranchPipelineConfigXml(config string) (*devops.MultiBranchPipeline, error) {
	pipeline := &devops.MultiBranchPipeline{}
	config = replaceXmlVersion(config, "1.1", "1.0")
	doc := etree.NewDocument()
	err := doc.ReadFromString(config)
	if err != nil {
		return nil, err
	}
	project := doc.SelectElement("org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject")
	if project == nil {
		return nil, fmt.Errorf("can not parse mutibranch pipeline config")
	}
	if properties := project.SelectElement("properties"); properties != nil {
		if multibranchTrigger := properties.SelectElement(
			"org.jenkinsci.plugins.workflow.multibranch.PipelineTriggerProperty"); multibranchTrigger != nil {
			pipeline.MultiBranchJobTrigger = getMultiBranchJobTriggerfromEtree(properties)
		}
	}
	pipeline.Description = project.SelectElement("description").Text()

	if discarder := project.SelectElement("orphanedItemStrategy"); discarder != nil {
		pipeline.Discarder = &devops.DiscarderProperty{
			DaysToKeep: discarder.SelectElement("daysToKeep").Text(),
			NumToKeep:  discarder.SelectElement("numToKeep").Text(),
		}
	}
	if triggers := project.SelectElement("triggers"); triggers != nil {
		if timerTrigger := triggers.SelectElement(
			"com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger"); timerTrigger != nil {
			pipeline.TimerTrigger = &devops.TimerTrigger{
				Interval: timerTrigger.SelectElement("interval").Text(),
			}
		}
	}

	if sources := project.SelectElement("sources"); sources != nil {
		if sourcesData := sources.SelectElement("data"); sourcesData != nil {
			if branchSource := sourcesData.SelectElement("jenkins.branch.BranchSource"); branchSource != nil {
				source := branchSource.SelectElement("source")
				switch source.SelectAttr("class").Value {
				case "org.jenkinsci.plugins.github_branch_source.GitHubSCMSource":
					pipeline.GitHubSource = getGithubSourcefromEtree(source)
					pipeline.SourceType = "github"
				case "com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource":
					pipeline.BitbucketServerSource = getBitbucketServerSourceFromEtree(source)
					pipeline.SourceType = "bitbucket_server"

				case "jenkins.plugins.git.GitSCMSource":
					pipeline.SourceType = "git"
					pipeline.GitSource = getGitSourcefromEtree(source)

				case "jenkins.scm.impl.SingleSCMSource":
					pipeline.SourceType = "single_svn"
					pipeline.SingleSvnSource = getSingleSvnSourceFromEtree(source)

				case "jenkins.scm.impl.subversion.SubversionSCMSource":
					pipeline.SourceType = "svn"
					pipeline.SvnSource = getSvnSourcefromEtree(source)
				}
			}
		}
	}

	pipeline.ScriptPath = project.SelectElement("factory").SelectElement("scriptPath").Text()
	return pipeline, nil
}

func toCrontab(millis int64) string {
	if millis*time.Millisecond.Nanoseconds() <= 5*time.Minute.Nanoseconds() {
		return "* * * * *"
	}
	if millis*time.Millisecond.Nanoseconds() <= 30*time.Minute.Nanoseconds() {
		return "H/5 * * * *"
	}
	if millis*time.Millisecond.Nanoseconds() <= 1*time.Hour.Nanoseconds() {
		return "H/15 * * * *"
	}
	if millis*time.Millisecond.Nanoseconds() <= 8*time.Hour.Nanoseconds() {
		return "H/30 * * * *"
	}
	if millis*time.Millisecond.Nanoseconds() <= 24*time.Hour.Nanoseconds() {
		return "H H/4 * * *"
	}
	if millis*time.Millisecond.Nanoseconds() <= 48*time.Hour.Nanoseconds() {
		return "H H/12 * * *"
	}
	return "H H * * *"

}
