package cloud_provider

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/juju/errors"
	log "github.com/sirupsen/logrus"

	"github.com/tidbcloud/auto-deploy/internal/types"
	"github.com/tidbcloud/auto-deploy/internal/utils"
)

const AZYRE_CONFIG_HOME = ".azure"

type azureCloudProvider struct {
	oidcProvider     types.OidcProvider
	account          types.Account
	profile          string
	tokenCache       string
	servicePrincipal string
}

func loadAzureConfig(configFilePath string) (string, error) {
	filePath := os.Getenv("HOME") + "/" + AZYRE_CONFIG_HOME + "/" + configFilePath
	bytes, err := os.ReadFile(filePath)
	return string(bytes), err
}

func NewAzureCloudProvider(oidcProvider types.OidcProvider, acc types.Account, region string) (types.CloudProvider, error) {
	token, err := oidcProvider.GetIdToken()
	if err != nil {
		return nil, err
	}
	log.WithField("token", token).Debug("Get id token")

	// check if the account is login
	type user struct {
		Name string `json:"name"`
		Type string `json:"type"`
	}
	type account struct {
		SubscriptionId string `json:"id"`
		TenantId       string `json:"tenantId"`
		User           user   `json:"user"`
	}
	resp, err := utils.RunCommandAndGetOutput("az", "account", "show")
	if err != nil {
		return nil, errors.Annotatef(err, "Please login to azure with 'az login'")
	}
	log.WithField("local-account", resp).Debug("Get local account")
	var localAcc account
	err = json.Unmarshal([]byte(resp), &localAcc)
	if err != nil {
		return nil, errors.Annotatef(err, "Failed to parse local account")
	}
	if localAcc.TenantId != acc.AzureClientId.TenantId ||
		localAcc.SubscriptionId != acc.AzureClientId.SubscriptionId ||
		localAcc.User.Name != acc.AzureClientId.ClientId {
		return nil, errors.Errorf("Account not match, please login with the right account")
	}

	// read azure config from local
	profile, err := loadAzureConfig("azureProfile.json")
	if err != nil {
		return nil, err
	}
	tokenCache, err := loadAzureConfig("msal_token_cache.json")
	if err != nil {
		return nil, err
	}
	servicePrincipal, err := loadAzureConfig("service_principal_entries.json")
	if err != nil && !os.IsNotExist(err) { // there is no service_principal_entries.json for local test
		return nil, err
	}

	return &azureCloudProvider{
		oidcProvider:     oidcProvider,
		account:          acc,
		profile:          profile,
		tokenCache:       tokenCache,
		servicePrincipal: servicePrincipal,
	}, nil
}

func (a *azureCloudProvider) GetCloudProviderType() types.CloudProviderType {
	return types.Azure
}

func (a *azureCloudProvider) GetAuthConfigPath(homeDir string) string {
	return homeDir + "/" + AZYRE_CONFIG_HOME
}

func (a *azureCloudProvider) GetAuthConfigSubPath() []string {
	if a.servicePrincipal != "" {
		return []string{"azureProfile.json", "msal_token_cache.json", "service_principal_entries.json"}
	} else {
		return []string{"azureProfile.json", "msal_token_cache.json"}
	}
}

func (a *azureCloudProvider) GetAuthConfigYaml(name string, accounts []types.Account) string {
	cred := `
apiVersion: v1
kind: Secret
type: Opaque
metadata:
    name: ` + name + `
stringData:
    azureProfile.json: |
` + utils.GetContentWithIndent(8, a.profile) + `
    msal_token_cache.json: |
` + utils.GetContentWithIndent(8, a.tokenCache)
	if a.servicePrincipal != "" {
		cred += `
    service_principal_entries.json: |
` + utils.GetContentWithIndent(8, a.servicePrincipal)
	}
	return cred
}

func (a *azureCloudProvider) WriteToLocal(homeDir string, accounts []types.Account) error {
	// azure login is not implemented in cli, don't need to write to local file
	return nil
}

func (a *azureCloudProvider) CreateK8s(k8s *types.K8s) error {
	oidcIssuer, err := utils.RunCommandAndGetOutput("./scripts/get-aks-info.sh", "oidc-issuer", k8s.Name)
	if err == nil {
		log.WithField("name", k8s.Name).Info("Clsuter already exists")
		k8s.OidcIssuer = oidcIssuer
		a.getPublicIp(k8s)
		a.getEndpoint(k8s)
		return errors.AlreadyExistsf("Cluster %s", k8s.Name)
	}

	args, err := getCreateK8sArgs(k8s)
	if err != nil {
		return err
	}

	exitCode, err := utils.RunCommand("./scripts/create-aks.sh",
		utils.LogWithPrefix(log.InfoLevel, fmt.Sprintf("create-%s-output: ", k8s.Name)), args)
	if err != nil {
		return errors.Annotatef(err, "k8sName=%s", k8s.Name)
	}
	if exitCode != 0 {
		return errors.Errorf("Failed to create k8s %s, exitCode=%d", k8s.Name, exitCode)
	}
	log.WithFields(log.Fields{
		"name":    k8s.Name,
		"version": k8s.Version,
		"region":  k8s.Region.Region,
	}).Info("Created K8s")

	oidcIssuer, err = utils.RunCommandAndGetOutput("./scripts/get-aks-info.sh", "oidc-issuer", k8s.Name)
	if err != nil {
		return err
	}
	k8s.OidcIssuer = oidcIssuer

	a.getPublicIp(k8s)
	a.getEndpoint(k8s)
	log.WithFields(log.Fields{
		"oidc-isuer": oidcIssuer,
		"public-ip":  k8s.PublicIps,
		"endpoint":   k8s.EndPoint,
	}).Debug("Get kubeconfig")
	return nil
}

func (a *azureCloudProvider) getEndpoint(k8s *types.K8s) {
	endpoint, err := utils.RunCommandAndGetOutput("./scripts/get-aks-info.sh", "endpoint", k8s.Name)
	if err == nil {
		k8s.EndPoint = endpoint
	}
	log.WithField("endpoint", endpoint).Debug("Get endpoint")
}

func (a *azureCloudProvider) getPublicIp(k8s *types.K8s) {
	publicIp, err := utils.RunCommandAndGetOutput("./scripts/get-aks-info.sh", "public-ip", k8s.Name)
	if err == nil {
		k8s.PublicIps = strings.Split(publicIp, "\n")
	}
	log.WithField("public-ip", publicIp).Debug("Get public ip")
}

func (a *azureCloudProvider) DestroyK8s(k8s *types.K8s) error {
	args, err := getCreateK8sArgs(k8s)
	if err != nil {
		return err
	}

	exitCode, err := utils.RunCommand("./scripts/destroy-aks.sh",
		utils.LogWithPrefix(log.InfoLevel, fmt.Sprintf("destroy-%s-output: ", k8s.Name)),
		args)
	if err != nil {
		return errors.Annotatef(err, "k8sName=%s", k8s.Name)
	}
	if exitCode != 0 {
		return errors.Errorf("Failed to create k8s %s, exitCode=%d", k8s.Name, exitCode)
	}
	log.WithFields(log.Fields{
		"name":   k8s.Name,
		"region": k8s.Region.Region,
	}).Info("Destroyed K8s")
	return nil
}

// Generate kubeconfig file
func (a *azureCloudProvider) GetKubeConfig(k8s *types.K8s) (string, error) {
	return utils.RunCommandAndGetOutput("./scripts/get-aks-info.sh", "kubeconfig", k8s.Name)
}
