package ionoscloud

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"gopkg.in/yaml.v3"

	"github.com/ionos-cloud/terraform-provider-ionoscloud/v6/services"

	ionoscloud "github.com/ionos-cloud/sdk-go/v6"
)

type KubeConfig struct {
	ApiVersion string `yaml:"apiVersion"`
	Clusters   []struct {
		Name    string
		Cluster struct {
			CaData string `yaml:"certificate-authority-data"`
			Server string
		}
	}
	Contexts []struct {
		Name    string
		Context struct {
			Cluster string
			User    string
		}
	}
	CurrentContext string `yaml:"current-context"`
	Kind           string
	Users          []struct {
		Name string
		User struct {
			Token string
		}
	}
	// preferences - add it when its structure is clear
}

func dataSourceK8sClusterSchema() map[string]*schema.Schema {
	return map[string]*schema.Schema{
		"id": {
			Type:     schema.TypeString,
			Optional: true,
		},
		"name": {
			Type:     schema.TypeString,
			Optional: true,
		},
		"state": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"k8s_version": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"maintenance_window": {
			Type:        schema.TypeList,
			Description: "A maintenance window comprise of a day of the week and a time for maintenance to be allowed",
			Computed:    true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"time": {
						Type:        schema.TypeString,
						Description: "A clock time in the day when maintenance is allowed",
						Computed:    true,
					},
					"day_of_the_week": {
						Type:        schema.TypeString,
						Description: "Day of the week when maintenance is allowed",
						Computed:    true,
					},
				},
			},
		},
		"config": {
			Type:      schema.TypeList,
			Computed:  true,
			Sensitive: true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"api_version": {
						Type:      schema.TypeString,
						Computed:  true,
						Sensitive: true,
					},
					"current_context": {
						Type:      schema.TypeString,
						Computed:  true,
						Sensitive: true,
					},
					"kind": {
						Type:     schema.TypeString,
						Computed: true,
					},
					"users": {
						Type:     schema.TypeList,
						Computed: true,
						Elem: &schema.Resource{
							Schema: map[string]*schema.Schema{
								"name": {
									Type:      schema.TypeString,
									Computed:  true,
									Sensitive: true,
								},
								"user": {
									Type:      schema.TypeMap,
									Computed:  true,
									Sensitive: true,
									Elem: &schema.Schema{
										Type: schema.TypeString,
									},
								},
							},
						},
					},
					"clusters": {
						Type:     schema.TypeList,
						Computed: true,
						Elem: &schema.Resource{
							Schema: map[string]*schema.Schema{
								"name": {
									Type:      schema.TypeString,
									Computed:  true,
									Sensitive: true,
								},
								"cluster": {
									Type:      schema.TypeMap,
									Computed:  true,
									Sensitive: true,
									Elem: &schema.Schema{
										Type: schema.TypeString,
									},
								},
							},
						},
					},
					"contexts": {
						Type:     schema.TypeList,
						Computed: true,
						Elem: &schema.Resource{
							Schema: map[string]*schema.Schema{
								"name": {
									Type:      schema.TypeString,
									Computed:  true,
									Sensitive: true,
								},
								"context": {
									Type:      schema.TypeMap,
									Computed:  true,
									Sensitive: true,
									Elem: &schema.Schema{
										Type: schema.TypeString,
									},
								},
							},
						},
					},
				},
			},
		},
		"user_tokens": {
			Type:      schema.TypeMap,
			Sensitive: true,
			Computed:  true,
			Elem: &schema.Schema{
				Type:      schema.TypeString,
				Sensitive: true,
			},
		},
		"ca_crt": {
			Type:      schema.TypeString,
			Sensitive: true,
			Computed:  true,
		},
		"server": {
			Type:      schema.TypeString,
			Sensitive: true,
			Computed:  true,
		},
		"available_upgrade_versions": {
			Type:        schema.TypeList,
			Description: "A list of available versions for upgrading the cluster",
			Computed:    true,
			Elem: &schema.Schema{
				Type: schema.TypeString,
			},
		},
		"viable_node_pool_versions": {
			Type:        schema.TypeList,
			Description: "A list of versions that may be used for node pools under this cluster",
			Computed:    true,
			Elem: &schema.Schema{
				Type: schema.TypeString,
			},
		},
		"public": {
			Type:        schema.TypeBool,
			Description: "The indicator if the cluster is public or private.",
			Computed:    true,
		},
		"nat_gateway_ip": {
			Type:        schema.TypeString,
			Description: "The NAT gateway IP of the cluster if the cluster is private.",
			Computed:    true,
		},
		"node_subnet": {
			Type:        schema.TypeString,
			Description: "The node subnet of the cluster, if the cluster is private.",
			Computed:    true,
		},
		"location": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"node_pools": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeString,
			},
		},
		"kube_config": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"api_subnet_allow_list": {
			Type: schema.TypeList,
			Description: "Access to the K8s API server is restricted to these CIDRs. Cluster-internal traffic is not " +
				"affected by this restriction. If no allowlist is specified, access is not restricted. If an IP " +
				"without subnet mask is provided, the default value will be used: 32 for IPv4 and 128 for IPv6.",
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeString,
			},
		},
		"s3_buckets": {
			Type:        schema.TypeList,
			Description: "List of Object Storage bucket configured for K8s usage. For now it contains only an Object Storage bucket used to store K8s API audit logs.",
			Computed:    true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"name": {
						Type:        schema.TypeString,
						Description: "Name of the Object Storage bucket",
						Required:    true,
					},
				},
			},
		},
	}
}

func dataSourceK8sCluster() *schema.Resource {
	return &schema.Resource{
		ReadContext: dataSourceK8sReadCluster,
		Schema:      dataSourceK8sClusterSchema(),
		Timeouts:    &resourceDefaultTimeouts,
	}
}

func dataSourceK8sReadCluster(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	client := meta.(services.SdkBundle).CloudApiClient

	id, idOk := d.GetOk("id")
	name, nameOk := d.GetOk("name")

	if idOk && nameOk {
		return diag.FromErr(errors.New("id and name cannot be both specified in the same time"))
	}
	if !idOk && !nameOk {
		return diag.FromErr(errors.New("please provide either the k8s cluster id or name"))
	}
	var cluster ionoscloud.KubernetesCluster
	var err error
	var apiResponse *ionoscloud.APIResponse

	if idOk {
		/* search by ID */
		cluster, apiResponse, err = client.KubernetesApi.K8sFindByClusterId(ctx, id.(string)).Execute()
		logApiRequestTime(apiResponse)
		if err != nil {
			return diag.FromErr(fmt.Errorf("an error occurred while fetching the k8s cluster with ID %s: %w", id.(string), err))
		}
	} else {
		/* search by name */
		var clusters ionoscloud.KubernetesClusters

		clusters, apiResponse, err := client.KubernetesApi.K8sGet(ctx).Depth(1).Execute()
		logApiRequestTime(apiResponse)
		if err != nil {
			return diag.FromErr(fmt.Errorf("an error occurred while fetching k8s clusters: %w", err))
		}

		if clusters.Items != nil {
			var results []ionoscloud.KubernetesCluster

			for _, c := range *clusters.Items {
				if c.Properties != nil && c.Properties.Name != nil && *c.Properties.Name == name.(string) {
					tmpCluster, apiResponse, err := client.KubernetesApi.K8sFindByClusterId(ctx, *c.Id).Execute()
					logApiRequestTime(apiResponse)
					if err != nil {
						return diag.FromErr(fmt.Errorf("an error occurred while fetching k8s cluster with ID %s: %w", *c.Id, err))
					}
					results = append(results, tmpCluster)
					break
				}
			}

			if results == nil || len(results) == 0 {
				return diag.FromErr(fmt.Errorf("no cluster found with the specified name %s", name.(string)))
			} else if len(results) > 1 {
				return diag.FromErr(fmt.Errorf("more than one cluster found with the specified name %s", name.(string)))
			} else {
				cluster = results[0]
			}
		}

	}

	if err = setK8sClusterData(d, &cluster); err != nil {
		return diag.FromErr(err)
	}

	if err = setAdditionalK8sClusterData(d, &cluster, client); err != nil {
		return diag.FromErr(err)
	}

	return nil
}

func setK8sConfigData(d *schema.ResourceData, configStr string) error {

	kubeConfig, err := parseClusterKubeconfig(configStr)
	if err != nil {
		return err
	}

	var server, caCrt string
	userTokens := map[string]string{}
	configMap := make(map[string]interface{})

	configMap["api_version"] = kubeConfig.ApiVersion
	configMap["current_context"] = kubeConfig.CurrentContext
	configMap["kind"] = kubeConfig.Kind

	// Managed K8s clusters each have their own unique kubeconfig so there is only 1 Clusters entry
	if len(kubeConfig.Clusters) != 0 {
		caData := kubeConfig.Clusters[0].Cluster.CaData
		decodedCrt := make([]byte, base64.StdEncoding.DecodedLen(len(caData)))
		if _, err := base64.StdEncoding.Decode(decodedCrt, []byte(caData)); err != nil {
			return err
		}
		caCrt = string(decodedCrt)
		server = kubeConfig.Clusters[0].Cluster.Server
		configMap["clusters"] = []map[string]interface{}{
			{"name": kubeConfig.Clusters[0].Name, "cluster": map[string]string{"server": server, "certificate_authority_data": caCrt}},
		}
	}

	contextsList := make([]map[string]interface{}, len(kubeConfig.Contexts))
	for i, contextVal := range kubeConfig.Contexts {
		contextsList[i] = map[string]interface{}{
			"name": contextVal.Name,
			"context": map[string]string{
				"cluster": contextVal.Context.Cluster,
				"user":    contextVal.Context.User,
			},
		}
	}

	configMap["contexts"] = contextsList

	userList := make([]map[string]interface{}, len(kubeConfig.Users))
	for i, user := range kubeConfig.Users {
		userList[i] = map[string]interface{}{
			"name": user.Name,
			"user": map[string]interface{}{
				"token": user.User.Token,
			},
		}

		userTokens[user.Name] = user.User.Token
	}

	configMap["users"] = userList

	configList := []map[string]interface{}{configMap}

	if err := d.Set("config", configList); err != nil {
		return err
	}
	if err := d.Set("user_tokens", userTokens); err != nil {
		return err
	}
	if err := d.Set("server", server); err != nil {
		return err
	}
	if err := d.Set("ca_crt", caCrt); err != nil {
		return err
	}

	return nil
}

func setAdditionalK8sClusterData(d *schema.ResourceData, cluster *ionoscloud.KubernetesCluster, client *ionoscloud.APIClient) error {

	if cluster.Metadata != nil {
		if cluster.Metadata.State != nil {
			if err := d.Set("state", *cluster.Metadata.State); err != nil {
				return err
			}
		}

	}

	ctx, cancel := context.WithTimeout(context.Background(), *resourceDefaultTimeouts.Default)

	if cancel != nil {
		defer cancel()
	}

	/* get and set the kubeconfig and nodepools*/
	if cluster.Id != nil {
		kubeConfig, apiResponse, err := client.KubernetesApi.K8sKubeconfigGet(ctx, *cluster.Id).Execute()
		logApiRequestTime(apiResponse)
		if err != nil {
			return fmt.Errorf("an error occurred while fetching the kubernetes config for cluster with ID %s: %w", *cluster.Id, err)
		}

		if err := d.Set("kube_config", kubeConfig); err != nil {
			return err
		}

		if err := setK8sConfigData(d, kubeConfig); err != nil {
			return err
		}

		/* getting node pools */
		clusterNodePools, apiResponse, err := client.KubernetesApi.K8sNodepoolsGet(ctx, *cluster.Id).Execute()
		logApiRequestTime(apiResponse)
		if err != nil {
			return fmt.Errorf("an error occurred while fetching the kubernetes cluster node pools for cluster with ID %s: %w", *cluster.Id, err)
		}

		if clusterNodePools.Items != nil && len(*clusterNodePools.Items) > 0 {
			var nodePools []interface{}
			for _, nodePool := range *clusterNodePools.Items {
				nodePools = append(nodePools, *nodePool.Id)
			}
			if err := d.Set("node_pools", nodePools); err != nil {
				return err
			}
		}

		if cluster.Properties != nil && cluster.Properties.AvailableUpgradeVersions != nil {
			availableUpgradeVersions := make([]interface{}, len(*cluster.Properties.AvailableUpgradeVersions), len(*cluster.Properties.AvailableUpgradeVersions))
			for i, availableUpgradeVersion := range *cluster.Properties.AvailableUpgradeVersions {
				availableUpgradeVersions[i] = availableUpgradeVersion
			}
			if err := d.Set("available_upgrade_versions", availableUpgradeVersions); err != nil {
				return err
			}
		}
	}

	return nil
}

func parseClusterKubeconfig(configStr string) (KubeConfig, error) {
	var kubeConfig KubeConfig
	err := yaml.Unmarshal([]byte(configStr), &kubeConfig)
	if err != nil {
		err = fmt.Errorf("error parsing cluster kubeconfig: %w", err)
	}
	return kubeConfig, err
}
