package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http/cookiejar"
	"strings"
	"time"

	"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 abapEnvironmentCreateTag(config abapEnvironmentCreateTagOptions, telemetryData *telemetry.CustomData) {

	c := command.Command{}

	c.Stdout(log.Writer())
	c.Stderr(log.Writer())

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

	client := piperhttp.Client{}

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

func runAbapEnvironmentCreateTag(config *abapEnvironmentCreateTagOptions, telemetryData *telemetry.CustomData, com abaputils.Communication, client piperhttp.Sender) error {

	connectionDetails, errorGetInfo := com.GetAbapCommunicationArrangementInfo(convertTagConfig(config), "")
	if errorGetInfo != nil {
		return errors.Wrap(errorGetInfo, "Parameters for the ABAP Connection not available")
	}

	// Configuring the HTTP Client and CookieJar
	cookieJar, errorCookieJar := cookiejar.New(nil)
	if errorCookieJar != nil {
		return errors.Wrap(errorCookieJar, "Could not create a Cookie Jar")
	}

	client.SetOptions(piperhttp.ClientOptions{
		MaxRequestDuration: 180 * time.Second,
		CookieJar:          cookieJar,
		Username:           connectionDetails.User,
		Password:           connectionDetails.Password,
	})

	backlog, errorPrepare := prepareBacklog(config)
	if errorPrepare != nil {
		return fmt.Errorf("Something failed during the tag creation: %w", errorPrepare)
	}

	return createTags(backlog, telemetryData, connectionDetails, client, com)
}

func createTags(backlog []CreateTagBacklog, telemetryData *telemetry.CustomData, con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {

	connection := con
	connection.XCsrfToken = "fetch"
	connection.URL = con.URL + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Tags"
	resp, err := abaputils.GetHTTPResponse("HEAD", connection, nil, client)
	if err != nil {
		return abaputils.HandleHTTPError(resp, err, "Authentication on the ABAP system failed", con)
	}
	defer resp.Body.Close()

	log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", connection.URL).Debug("Authentication on the ABAP system successful")
	connection.XCsrfToken = resp.Header.Get("X-Csrf-Token")

	errorOccurred := false
	for _, item := range backlog {
		err = createTagsForSingleItem(item, telemetryData, connection, client, com)
		if err != nil {
			errorOccurred = true
		}
	}

	if errorOccurred {
		message := "At least one tag has not been created"
		log.Entry().Errorf(message)
		return errors.New(message)
	}
	return nil

}

func createTagsForSingleItem(item CreateTagBacklog, telemetryData *telemetry.CustomData, con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {

	errorOccurred := false
	for index := range item.tags {
		err = createSingleTag(item, index, telemetryData, con, client, com)
		if err != nil {
			errorOccurred = true
		}
	}
	if errorOccurred {
		message := "At least one tag has not been created"
		err = errors.New(message)
	}
	return err
}

func createSingleTag(item CreateTagBacklog, index int, telemetryData *telemetry.CustomData, con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {

	requestBodyStruct := CreateTagBody{RepositoryName: item.repositoryName, CommitID: item.commitID, Tag: item.tags[index].tagName, Description: item.tags[index].tagDescription}
	requestBodyJson, err := json.Marshal(&requestBodyStruct)
	if err != nil {
		return err
	}

	log.Entry().Debugf("Request body: %s", requestBodyJson)
	resp, err := abaputils.GetHTTPResponse("POST", con, requestBodyJson, client)
	if err != nil {
		errorMessage := "Could not create tag " + requestBodyStruct.Tag + " for repository " + requestBodyStruct.RepositoryName + " with commitID " + requestBodyStruct.CommitID
		err = abaputils.HandleHTTPError(resp, err, errorMessage, con)
		return err
	}
	defer resp.Body.Close()

	// Parse response
	var createTagResponse CreateTagResponse
	var abapResp map[string]*json.RawMessage
	bodyText, _ := ioutil.ReadAll(resp.Body)

	if err = json.Unmarshal(bodyText, &abapResp); err != nil {
		return err
	}
	if err = json.Unmarshal(*abapResp["d"], &createTagResponse); err != nil {
		return err
	}

	con.URL = con.Host + "/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull(guid'" + createTagResponse.UUID + "')"
	err = checkStatus(con, client, com)

	if err == nil {
		log.Entry().Info("Created tag " + requestBodyStruct.Tag + " for repository " + requestBodyStruct.RepositoryName + " with commitID " + requestBodyStruct.CommitID)
	} else {
		log.Entry().Error("NOT created: Tag " + requestBodyStruct.Tag + " for repository " + requestBodyStruct.RepositoryName + " with commitID " + requestBodyStruct.CommitID)
	}

	return err
}

func checkStatus(con abaputils.ConnectionDetailsHTTP, client piperhttp.Sender, com abaputils.Communication) (err error) {
	var status string
	pollIntervall := com.GetPollIntervall()
	count := 0
	for {
		count += 1
		entity, _, err := abaputils.GetStatus("Could not create Tag", con, client)
		if err != nil {
			return err
		}
		status = entity.Status
		if status != "R" {
			if status == "E" {
				err = errors.New("Could not create Tag")
			}
			return err
		}
		if count >= 200 {
			return errors.New("Could not create Tag (Timeout)")
		}
		time.Sleep(pollIntervall)
	}
}

func prepareBacklog(config *abapEnvironmentCreateTagOptions) (backlog []CreateTagBacklog, err error) {

	if config.Repositories != "" && config.RepositoryName != "" {
		return nil, errors.New("Configuring the parameter repositories and the parameter repositoryName at the same time is not allowed")
	}

	if config.RepositoryName != "" && config.CommitID != "" {
		backlog = append(backlog, CreateTagBacklog{repositoryName: config.RepositoryName, commitID: config.CommitID})
	}

	if config.Repositories != "" {
		descriptor, err := abaputils.ReadAddonDescriptor(config.Repositories) //config.Repositories should contain a file name
		if err != nil {
			return nil, err
		}
		for _, repo := range descriptor.Repositories {
			backlogInstance := CreateTagBacklog{repositoryName: repo.Name, commitID: repo.CommitID}
			if config.GenerateTagForAddonComponentVersion && repo.VersionYAML != "" {
				tag := Tag{tagName: "v" + repo.VersionYAML, tagDescription: "Generated by the ABAP Environment Pipeline"}
				backlogInstance.tags = append(backlogInstance.tags, tag)
			}
			backlog = append(backlog, backlogInstance)
		}
		if config.GenerateTagForAddonProductVersion {
			if descriptor.AddonProduct != "" && descriptor.AddonVersionYAML != "" {
				addonProductDash := strings.Replace(descriptor.AddonProduct, "/", "-", 2)
				backlog = addTagToList(backlog, addonProductDash+"-"+descriptor.AddonVersionYAML, "Generated by the ABAP Environment Pipeline")
			} else {
				log.Entry().WithField("generateTagForAddonProductVersion", config.GenerateTagForAddonProductVersion).WithField("AddonProduct", descriptor.AddonProduct).WithField("AddonVersion", descriptor.AddonVersionYAML).Infof("Not all required values are provided to create an addon product version tag")
			}
		}
	}
	if config.TagName != "" {
		backlog = addTagToList(backlog, config.TagName, config.TagDescription)
	}
	return backlog, nil
}

func addTagToList(backlog []CreateTagBacklog, tag string, description string) []CreateTagBacklog {

	for i, item := range backlog {
		tag := Tag{tagName: tag, tagDescription: description}
		backlog[i].tags = append(item.tags, tag)
	}
	return backlog
}

func convertTagConfig(config *abapEnvironmentCreateTagOptions) abaputils.AbapEnvironmentOptions {
	subOptions := abaputils.AbapEnvironmentOptions{}

	subOptions.CfAPIEndpoint = config.CfAPIEndpoint
	subOptions.CfServiceInstance = config.CfServiceInstance
	subOptions.CfServiceKeyName = config.CfServiceKeyName
	subOptions.CfOrg = config.CfOrg
	subOptions.CfSpace = config.CfSpace
	subOptions.Host = config.Host
	subOptions.Password = config.Password
	subOptions.Username = config.Username

	return subOptions
}

type CreateTagBacklog struct {
	repositoryName string
	commitID       string
	tags           []Tag
}

type Tag struct {
	tagName        string
	tagDescription string
}

type CreateTagBody struct {
	RepositoryName string `json:"sc_name"`
	CommitID       string `json:"commit_id"`
	Tag            string `json:"tag_name"`
	Description    string `json:"tag_description"`
}

type CreateTagResponse struct {
	UUID string `json:"uuid"`
}
