package cmd

import (
	"encoding/json"
	"time"

	abapbuild "github.com/SAP/jenkins-library/pkg/abap/build"
	"github.com/SAP/jenkins-library/pkg/abaputils"
	"github.com/SAP/jenkins-library/pkg/command"
	piperhttp "github.com/SAP/jenkins-library/pkg/http"
	"github.com/SAP/jenkins-library/pkg/log"
	"github.com/SAP/jenkins-library/pkg/telemetry"
	"github.com/pkg/errors"
)

func abapEnvironmentAssembleConfirm(config abapEnvironmentAssembleConfirmOptions, telemetryData *telemetry.CustomData, cpe *abapEnvironmentAssembleConfirmCommonPipelineEnvironment) {
	// for command execution use Command
	c := command.Command{}
	// reroute command output to logging framework
	c.Stdout(log.Writer())
	c.Stderr(log.Writer())

	var autils = abaputils.AbapUtils{
		Exec: &c,
	}

	client := piperhttp.Client{}
	err := runAbapEnvironmentAssembleConfirm(&config, telemetryData, &autils, &client, cpe)
	if err != nil {
		log.Entry().WithError(err).Fatal("step execution failed")
	}
}

func runAbapEnvironmentAssembleConfirm(config *abapEnvironmentAssembleConfirmOptions, telemetryData *telemetry.CustomData, com abaputils.Communication, client abapbuild.HTTPSendLoader, cpe *abapEnvironmentAssembleConfirmCommonPipelineEnvironment) error {
	conn := new(abapbuild.Connector)
	var connConfig abapbuild.ConnectorConfiguration
	connConfig.CfAPIEndpoint = config.CfAPIEndpoint
	connConfig.CfOrg = config.CfOrg
	connConfig.CfSpace = config.CfSpace
	connConfig.CfServiceInstance = config.CfServiceInstance
	connConfig.CfServiceKeyName = config.CfServiceKeyName
	connConfig.Host = config.Host
	connConfig.Username = config.Username
	connConfig.Password = config.Password
	connConfig.AddonDescriptor = config.AddonDescriptor
	connConfig.MaxRuntimeInMinutes = config.MaxRuntimeInMinutes
	connConfig.CertificateNames = config.CertificateNames

	err := conn.InitBuildFramework(connConfig, com, client)
	if err != nil {
		return err
	}
	var addonDescriptor abaputils.AddonDescriptor
	err = json.Unmarshal([]byte(config.AddonDescriptor), &addonDescriptor)
	if err != nil {
		return err
	}
	delayBetweenPosts := time.Duration(3 * time.Second)
	builds, err := startingConfirm(addonDescriptor.Repositories, *conn, delayBetweenPosts)
	if err != nil {
		return err
	}
	maxRuntimeInMinutes := time.Duration(config.MaxRuntimeInMinutes) * time.Minute
	pollInterval := time.Duration(60 * time.Second)
	err = polling(builds, maxRuntimeInMinutes, pollInterval)
	if err != nil {
		return err
	}
	err = checkIfFailedAndPrintLogs(builds)
	if err != nil {
		return err
	}
	return nil
}

func startingConfirm(repos []abaputils.Repository, conn abapbuild.Connector, delayBetweenPosts time.Duration) ([]buildWithRepository, error) {
	var confirmedBuilds []buildWithRepository
	var releasePackagesFailed error = nil
	for _, repo := range repos {
		assemblyBuild := abapbuild.Build{
			Connector: conn,
		}
		buildRepo := buildWithRepository{
			build: assemblyBuild,
			repo:  repo,
		}
		if repo.InBuildScope && repo.Status == "R" {
			err := buildRepo.startConfirm()
			if err != nil {
				return confirmedBuilds, err
			}
			confirmedBuilds = append(confirmedBuilds, buildRepo)
		} else if repo.InBuildScope && repo.Status != "R" {
			errormessage := "Release of package '" + repo.PackageName + "' must have failed as still in status: '" + repo.Status + "'"
			if releasePackagesFailed == nil {
				releasePackagesFailed = errors.New(errormessage)
			} else {
				releasePackagesFailed = errors.Wrapf(releasePackagesFailed, errormessage)
			}
		} else {
			log.Entry().Infof("Packages %s was not assembled in this pipeline run, thus no need to confirm", repo.PackageName)
		}

		//as batch events in the ABAP Backend need a little time
		time.Sleep(delayBetweenPosts)
	}
	return confirmedBuilds, releasePackagesFailed
}

func polling(builds []buildWithRepository, maxRuntimeInMinutes time.Duration, pollInterval time.Duration) error {
	timeout := time.After(maxRuntimeInMinutes)
	ticker := time.Tick(pollInterval)
	for {
		select {
		case <-timeout:
			return errors.New("Timed out")
		case <-ticker:
			var allFinished bool = true
			for i := range builds {
				if err := builds[i].build.Get(); err != nil {
					return err
				}
				if !builds[i].build.IsFinished() {
					log.Entry().Infof("Assembly of %s is not yet finished, check again in %s", builds[i].repo.PackageName, pollInterval)
					allFinished = false
				}
			}
			if allFinished {
				return nil
			}
		}
	}
}

func (b *buildWithRepository) startConfirm() error {
	if b.repo.Name == "" || b.repo.PackageName == "" {
		return errors.New("Parameters missing. Please provide software component name, namespace and packagename")
	}
	valuesInput := abapbuild.Values{
		Values: []abapbuild.Value{
			{
				ValueID: "SWC",
				Value:   b.repo.Name,
			},
		},
	}
	if b.repo.Namespace != "" {
		// Steampunk Use Case, Namespace provided by AAKaaS
		valuesInput.Values = append(valuesInput.Values,
			abapbuild.Value{ValueID: "SSDC-delta",
				Value: b.repo.Namespace + b.repo.PackageName})
	} else {
		// Traditional SWCs, Namespace to be provided in assembly system via build script
		valuesInput.Values = append(valuesInput.Values,
			abapbuild.Value{ValueID: "PACKAGE_TYPE",
				Value: b.repo.PackageType})
		valuesInput.Values = append(valuesInput.Values,
			abapbuild.Value{ValueID: "PACKAGE_NAME_" + b.repo.PackageType,
				Value: b.repo.PackageName})
	}

	phase := "BUILD_CONFIRM"
	log.Entry().Infof("Starting confirmation of package %s", b.repo.PackageName)
	return b.build.Start(phase, valuesInput)
}
