package argohandler

import (
	"context"
	"encoding/base64"
	"fmt"
	"log"
	"main/db"
	"math/rand"
	"strings"
	"sync"
	"time"

	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
	argoprojv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

type ApplicationValues struct {
	Name      string `yaml:"name"`
	Namespace string `yaml:"namespace"`
	Path      string `yaml:"path"`
	Cluster   string `yaml:"cluster"`
	RepoURL   string `yaml:"repoURL"`
	Server    string `yaml:"server"`
	UserID    string `yaml:"userID"`
}

type Clusters struct {
	Name        string `yaml:"name"`
	ClusterID   string `yaml:"clusterid"`
	Status      string `yaml:"status"`
	Version     string `yaml:version`
	HealthCheck string `yaml:"healthcheck`
	Alert       string `yaml:"alert"`
	EndPoint    string `yaml:"endpoint"`
}

type HostConfig struct {
	Name       string `yaml:"name"`
	Kubeconfig string `yaml:"kubeconfig"`
}

type SyncTask struct {
	AppName     string
	Clustername string
	Namespace   string
	ObjectID    string
}

var (
	client apiclient.Client
	once   sync.Once
	err    error
)

func InitializeClient() {
	once.Do(func() {
		argocdServer := "192.168.2.172:32200"
		argocdToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJhZG1pbjphcGlLZXkiLCJuYmYiOjE3NDM4NTAwNzIsImlhdCI6MTc0Mzg1MDA3MiwianRpIjoiNWZhNmQ5MDgtMzljNi00ZWQ4LWE5YzgtMzI4YzMzYjkyNzk4In0.ZvhJk4L5vBQldtJyReKYXCQCWF8j8gHLZlY8PninSFA"

		config := apiclient.ClientOptions{
			ServerAddr: argocdServer,
			AuthToken:  argocdToken,
			Insecure:   true,
		}

		// Initialize the ArgoCD client
		client, err = apiclient.NewClient(&config)
		if err != nil {
			log.Fatalf("Failed to create ArgoCD client: %v", err)
		}
	})
}

func generateRandomString(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
	seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
	b := make([]byte, length)
	for i := range b {
		b[i] = charset[seededRand.Intn(len(charset))]
	}
	return string(b)
}

func CreateApp(objectID string, clustername string, ControlPlane string, PlatformVersion string, Cpu string, Memory string, userID string) {

	InitializeClient()

	// Create an application client
	_, appClient, err := client.NewApplicationClient()
	if err != nil {
		log.Fatalf("Failed to create application client: %v", err)
	}

	// Generate unique cluster name with user prefix
	uniqueClusterName := clustername
	namespace := "ns-" + generateRandomString(4)

	app := ApplicationValues{
		Name:      uniqueClusterName,
		Namespace: namespace,
		Path:      "vcluster-0.21.1",
		Cluster:   "bugx",
		Server:    "https://kubernetes.default.svc",
		RepoURL:   "http://192.168.2.20:8015/root/application.git",
		UserID:    userID,
	}

	// Define the ArgoCD application
	argoApp := &argoprojv1alpha1.Application{
		ObjectMeta: metav1.ObjectMeta{
			Name:      app.Name,
			Namespace: "argocd",
			Labels: map[string]string{
				"user-id":     userID,
				"type":        "vcluster",
				"clustername": uniqueClusterName,
				"namespace":   namespace,
			},
			Finalizers: []string{
				"resources-finalizer.argocd.argoproj.io",
			},
		},
		Spec: argoprojv1alpha1.ApplicationSpec{
			Destination: argoprojv1alpha1.ApplicationDestination{
				Namespace: app.Namespace,
				Name:      app.Cluster,
			},
			Source: &argoprojv1alpha1.ApplicationSource{
				Path:           app.Path,
				RepoURL:        app.RepoURL,
				TargetRevision: "HEAD",
				Helm: &argoprojv1alpha1.ApplicationSourceHelm{
					Parameters: []argoprojv1alpha1.HelmParameter{
						{
							Name:  "controlPlane.advanced.defaultImageRegistry",
							Value: "localhost:30516",
						},
						{
							Name:  "controlPlane.distro.k8s.version",
							Value: PlatformVersion,
						},
						{
							Name:  "controlPlane.distro.k8s.resources.limits.cpu",
							Value: Cpu,
						},
						{
							Name:  "controlPlane.distro.k8s.resources.limits.memory",
							Value: Memory + "Mi",
						},
						{
							Name:  "controlPlane.distro.k8s.resources.requests.cpu",
							Value: Cpu,
						},
						{
							Name:  "controlPlane.distro.k8s.resources.requests.memory",
							Value: Memory,
						},
						{
							Name:  "controlPlane.ingress.enabled",
							Value: "true",
						},
						{
							Name:  "controlPlane.ingress.host",
							Value: app.Namespace + ".bugx.ir",
						},
						{
							Name:  "controlPlane.proxy.extraSANs[0]",
							Value: app.Namespace + ".bugx.ir",
						},
						{
							Name:  "exportKubeConfig.server",
							Value: "https://" + app.Namespace + ".bugx.ir",
						},
					},
				},
			},
			Project: "default",
			SyncPolicy: &argoprojv1alpha1.SyncPolicy{
				SyncOptions: []string{"CreateNamespace=true"},
			},
		},
	}

	// Create the application
	createdApp, err := appClient.Create(context.Background(), &application.ApplicationCreateRequest{
		Application: argoApp,
	})
	if err != nil {
		log.Fatalf("Failed to create application: %v", err)
	}

	fmt.Printf("Application created: %s\n", createdApp.Name)

	SyncApp(objectID, app.Name, clustername, namespace)

}

func getConfig(objectID string, cluster string, namespace string) {
	var hostcluster HostConfig

	// "vc-"+cluster will be vcluster secret
	// kubectl get secret vc-test-prod  -n ns-ekf7 -o yaml
	err := db.Host_cluster_details.FindOne(context.TODO(), bson.M{"name": "host-cluster-1"}).Decode(&hostcluster)
	if err != nil {
		fmt.Println("Can't get host config file")
	}

	kubeconfigBytes := []byte(hostcluster.Kubeconfig)
	config, err := clientcmd.RESTConfigFromKubeConfig(kubeconfigBytes)
	if err != nil {
		log.Fatalf("Error loading kubeconfig from string %v", err)
	}

	clientset, err := kubernetes.NewForConfig(config)

	if err != nil {
		log.Fatalf("Error creating clientSet: %v", err)
	}

	secret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), "vc-"+cluster, metav1.GetOptions{})
	if err != nil {
		fmt.Println("Error in getting cluster config")
		return
	}

	configStrings := string(secret.Data["config"])
	configStrings = base64.StdEncoding.EncodeToString([]byte(configStrings))

	updateConfig(objectID, configStrings, namespace)

}

func updateConfig(objectID string, configStrings string, namespace string) {
	id, _ := primitive.ObjectIDFromHex(objectID)

	filter := bson.M{"_id": id}
	update := bson.M{
		"$set": bson.M{
			"cluster_config": configStrings,
			"namespace":      namespace,
		},
	}

	_, err = db.Vclusters_details.UpdateOne(context.TODO(), filter, update)
	if err != nil {
		fmt.Println("update cluster config error: ", err)
	}
}

func SyncApp(objectID string, appName string, cluster string, namesname string) {

	InitializeClient()

	// Create an application client
	_, appClient, err := client.NewApplicationClient()
	if err != nil {
		log.Fatalf("Failed to create application client: %v", err)
	}

	// Synchronize the application
	_, err = appClient.Sync(context.Background(), &application.ApplicationSyncRequest{
		Name: &appName,
	})

	if err != nil {
		log.Fatalf("Failed to sync application: %v", err)
	}

	var syncQueue chan SyncTask
	syncQueue = make(chan SyncTask, 100)

	go func() {
		for task := range syncQueue {
			for {
				app, err := appClient.Get(context.TODO(), &application.ApplicationQuery{Name: &task.AppName})
				if err != nil {
					log.Printf("Error fetching app %s: %v", task.AppName, err)
					break
				}

				status := app.Status.Sync.Status
				health := app.Status.Health.Status
				log.Printf("App %s: sync=%s, health=%s", task.AppName, status, health)

				if status == "Synced" && health == "Healthy" {
					log.Printf("App %s synced successfully", task.AppName)
					getConfig(task.ObjectID, task.Clustername, task.Namespace)
					break
				}

				if status == "Unknown" || health == "Degraded" {
					log.Printf("App %s sync might be stuck or failed", task.AppName)
				}

				time.Sleep(3 * time.Second)
			}
		}
	}()

	syncQueue <- SyncTask{
		AppName:     appName,
		Clustername: cluster,
		Namespace:   namesname,
		ObjectID:    objectID,
	}

	fmt.Printf("Application synced successfully: %s\n", appName)

}

func ListUserClusters(userID string) (error, []Clusters) {
	InitializeClient()

	_, appClient, err := client.NewApplicationClient()
	if err != nil {
		return fmt.Errorf("failed to create application client: %v", err), nil
	}

	// List all applications with the user's label
	selector := fmt.Sprintf("user-id=%s", userID)
	apps, err := appClient.List(context.Background(), &application.ApplicationQuery{
		Selector: &selector,
	})
	if err != nil {
		return fmt.Errorf("failed to list applications: %v", err), nil
	}

	clusters := []Clusters{}

	for _, app := range apps.Items {
		newCluster := Clusters{
			Name:      app.Name,
			Status:    string(app.Status.Health.Status),
			ClusterID: strings.Split(string(app.ObjectMeta.UID), "-")[0],
		}
		clusters = append(clusters, newCluster)
	}

	return nil, clusters
}
