package hooks

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

	"github.com/cloudfoundry/libbuildpack"
)

type Command interface {
	Execute(string, io.Writer, io.Writer, string, ...string) error
}

type AppdynamicsHook struct {
	libbuildpack.DefaultHook
	Log     *libbuildpack.Logger
	Command Command
}

type Plan struct {
	Credentials Credential `json:"credentials"`
}

type Credential struct {
	ControllerHost   string `json:"host-name"`
	ControllerPort   string `json:"port"`
	SslEnabled       bool   `json:"ssl-enabled"`
	AccountAccessKey string `json:"account-access-key"`
	AccountName      string `json:"account-name"`
}

type VcapApplication struct {
	ApplicationName string `json:"application_name"`
	ApplicationId   string `json:"application_id"`
}

func (h AppdynamicsHook) getEnv(key, fallback string) string {
	if value, ok := os.LookupEnv(key); ok {
		return value
	}
	return fallback
}

func (h AppdynamicsHook) GenerateAppdynamicsScript(envVars map[string]string) string {
	var envKeys []string
	for k := range envVars {
		envKeys = append(envKeys, k)
	}
	sort.Strings(envKeys) // unnecessary but just to be deterministic for tests

	scriptContents := "# Autogenerated Appdynamics Script \n"
	for _, envKey := range envKeys {
		envStr := fmt.Sprintf("export %s=%s", envKey, envVars[envKey])
		scriptContents += "\n" + envStr
	}
	return scriptContents
}

func (h AppdynamicsHook) CreateAppDynamicsEnv(stager *libbuildpack.Stager, environmentVars map[string]string) error {
	scriptContents := h.GenerateAppdynamicsScript(environmentVars)
	h.Log.BeginStep("Writing Appdynamics Environment")
	h.Log.Debug(scriptContents)
	return stager.WriteProfileD("appdynamics.sh", scriptContents)
}

func (h AppdynamicsHook) BeforeCompile(stager *libbuildpack.Stager) error {
	if os.Getenv("APPD_AGENT") != "" {
		// APPD_AGENT is set => multibuildpack is used to configure appdynamics agent. Do nothing.
		return nil
	}

	vcapServices := os.Getenv("VCAP_SERVICES")
	services := make(map[string][]Plan)

	err := json.Unmarshal([]byte(vcapServices), &services)
	if err != nil {
		h.Log.Debug("Could not unmarshall VCAP_SERVICES JSON exiting")
		return nil
	}

	if val, ok := services["appdynamics"]; ok { // carry the procedure only when Appdynamics service is bound.
		h.Log.Warning("[DEPRECATION WARNING]:")
		h.Log.Warning("Please use AppDynamics extension buildpack for Golang Application instrumentation")
		h.Log.Warning("for more details: https://docs.pivotal.io/partners/appdynamics/multibuildpack.html")

		h.Log.BeginStep("Setting up Appdynamics")

		appdynamicsPlan := val[0].Credentials
		vcapApplication := os.Getenv("VCAP_APPLICATION")
		application := VcapApplication{}

		err = json.Unmarshal([]byte(vcapApplication), &application)
		if err != nil {
			h.Log.Debug("Could not unmarshall VCAP_APPLICATION JSON")
		}

		sslFlag := "off"

		if appdynamicsPlan.SslEnabled {
			sslFlag = "on"
		}

		appdEnv := map[string]string{
			"APPD_APP_NAME":           h.getEnv("APPD_APP_NAME", application.ApplicationName),
			"APPD_TIER_NAME":          h.getEnv("APPD_TIER_NAME", application.ApplicationName),
			"APPD_NODE_NAME":          h.getEnv("APPD_NODE_NAME", application.ApplicationName),
			"APPD_CONTROLLER_HOST":    appdynamicsPlan.ControllerHost,
			"APPD_CONTROLLER_PORT":    appdynamicsPlan.ControllerPort,
			"APPD_ACCOUNT_ACCESS_KEY": appdynamicsPlan.AccountAccessKey,
			"APPD_ACCOUNT_NAME":       appdynamicsPlan.AccountName,
			"APPD_SSL_ENABLED":        sslFlag,
		}

		if err := h.CreateAppDynamicsEnv(stager, appdEnv); err != nil {
			h.Log.Error("Could not create Appdynamics environment: %v", err)
			return err
		}

	}
	return nil
}

func init() {
	logger := libbuildpack.NewLogger(os.Stdout)
	command := &libbuildpack.Command{}

	libbuildpack.AddHook(AppdynamicsHook{
		Log:     logger,
		Command: command,
	})
}
