package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"

	"github.com/SAP/jenkins-library/pkg/abaputils"
	"github.com/SAP/jenkins-library/pkg/cloudfoundry"
	"github.com/SAP/jenkins-library/pkg/command"
	"github.com/SAP/jenkins-library/pkg/log"
	"github.com/SAP/jenkins-library/pkg/telemetry"
	"github.com/ghodss/yaml"
	"github.com/google/uuid"
)

func abapEnvironmentCreateSystem(config abapEnvironmentCreateSystemOptions, telemetryData *telemetry.CustomData) {

	cf := cloudfoundry.CFUtils{Exec: &command.Command{}}
	u := &googleUUID{}

	// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
	err := runAbapEnvironmentCreateSystem(&config, telemetryData, cf, u)
	if err != nil {
		log.Entry().WithError(err).Fatal("step execution failed")
	}
}

func runAbapEnvironmentCreateSystem(config *abapEnvironmentCreateSystemOptions, telemetryData *telemetry.CustomData, cf cloudfoundry.CFUtils, u uuidGenerator) error {

	if config.ServiceManifest != "" {
		// if the manifest file is provided, it is directly passed through to cloudFoundryCreateService
		createServiceConfig := cloudFoundryCreateServiceOptions{
			CfAPIEndpoint:   config.CfAPIEndpoint,
			CfOrg:           config.CfOrg,
			CfSpace:         config.CfSpace,
			Username:        config.Username,
			Password:        config.Password,
			ServiceManifest: config.ServiceManifest,
		}
		return runCloudFoundryCreateService(&createServiceConfig, telemetryData, cf)
	}
	// if no manifest file is provided, it is created with the provided config values
	manifestYAML, err := generateManifestYAML(config)
	if err != nil {
		return err
	}

	// writing the yaml into a temporary file
	path, _ := os.Getwd()
	path = path + "/generated_service_manifest-" + u.getUUID() + ".yml"
	log.Entry().Debugf("Path: %s", path)
	err = ioutil.WriteFile(path, manifestYAML, 0644)
	if err != nil {
		log.SetErrorCategory(log.ErrorConfiguration)
		return fmt.Errorf("%s: %w", "Could not generate manifest file for the cloud foundry cli", err)
	}

	defer os.Remove(path)

	// Calling cloudFoundryCreateService with the respective parameters
	createServiceConfig := cloudFoundryCreateServiceOptions{
		CfAPIEndpoint:   config.CfAPIEndpoint,
		CfOrg:           config.CfOrg,
		CfSpace:         config.CfSpace,
		Username:        config.Username,
		Password:        config.Password,
		ServiceManifest: path,
	}
	return runCloudFoundryCreateService(&createServiceConfig, telemetryData, cf)
}

func generateManifestYAML(config *abapEnvironmentCreateSystemOptions) ([]byte, error) {
	addonProduct := ""
	addonVersion := ""
	parentSaaSAppName := ""
	if config.AddonDescriptorFileName != "" && config.IncludeAddon {
		descriptor, err := abaputils.ReadAddonDescriptor(config.AddonDescriptorFileName)
		if err != nil {
			log.SetErrorCategory(log.ErrorConfiguration)
			return nil, fmt.Errorf("Cloud not read addonProduct and addonVersion from %s: %w", config.AddonDescriptorFileName, err)
		}
		addonProduct = descriptor.AddonProduct
		addonVersion = descriptor.AddonVersionYAML
		parentSaaSAppName = "addon_test"

	}
	params := abapSystemParameters{
		AdminEmail:           config.AbapSystemAdminEmail,
		Description:          config.AbapSystemDescription,
		IsDevelopmentAllowed: &config.AbapSystemIsDevelopmentAllowed,
		SapSystemName:        config.AbapSystemID,
		SizeOfPersistence:    config.AbapSystemSizeOfPersistence,
		SizeOfRuntime:        config.AbapSystemSizeOfRuntime,
		AddonProductName:     addonProduct,
		AddonProductVersion:  addonVersion,
		ParentSaaSAppName:    parentSaaSAppName,
	}

	serviceParameters, err := json.Marshal(params)
	serviceParametersString := string(serviceParameters)
	log.Entry().Debugf("Service Parameters: %s", serviceParametersString)
	if err != nil {
		log.SetErrorCategory(log.ErrorConfiguration)
		return nil, fmt.Errorf("Could not generate parameter string for the cloud foundry cli: %w", err)
	}

	/*
		Generating the temporary manifest yaml file
	*/
	service := Service{
		Name:       config.CfServiceInstance,
		Broker:     config.CfService,
		Plan:       config.CfServicePlan,
		Parameters: serviceParametersString,
	}

	serviceManifest := serviceManifest{CreateServices: []Service{service}}
	errorMessage := "Could not generate manifest for the cloud foundry cli"

	// converting the golang structure to json
	manifestJSON, err := json.Marshal(serviceManifest)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errorMessage, err)
	}

	// converting the json to yaml
	manifestYAML, err := yaml.JSONToYAML(manifestJSON)
	if err != nil {
		return nil, fmt.Errorf("%s: %w", errorMessage, err)
	}

	log.Entry().Debug(string(manifestYAML))

	return manifestYAML, nil
}

type abapSystemParameters struct {
	AdminEmail           string `json:"admin_email,omitempty"`
	Description          string `json:"description,omitempty"`
	IsDevelopmentAllowed *bool  `json:"is_development_allowed,omitempty"`
	SapSystemName        string `json:"sapsystemname,omitempty"`
	SizeOfPersistence    int    `json:"size_of_persistence,omitempty"`
	SizeOfRuntime        int    `json:"size_of_runtime,omitempty"`
	AddonProductName     string `json:"addon_product_name,omitempty"`
	AddonProductVersion  string `json:"addon_product_version,omitempty"`
	ParentSaaSAppName    string `json:"parent_saas_appname,omitempty"`
}

type serviceManifest struct {
	CreateServices []Service `json:"create-services"`
}

// Service struct for creating a cloud foundry service
type Service struct {
	Name       string `json:"name"`
	Broker     string `json:"broker"`
	Plan       string `json:"plan"`
	Parameters string `json:"parameters,omitempty"`
}

type uuidGenerator interface {
	getUUID() string
}

type googleUUID struct {
}

func (u *googleUUID) getUUID() string {
	return uuid.New().String()
}
