package infrastructure

import (
	"context"
	"errors"
	"fmt"
	"oc-datacenter/conf"

	authv1 "k8s.io/api/authentication/v1"
	v1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

type KubernetesService struct {
	Set *kubernetes.Clientset
}

func NewKubernetesService() (Infrastructure, error) {
	config := &rest.Config{
		Host: conf.GetConfig().KubeHost + ":" + conf.GetConfig().KubePort,
		TLSClientConfig: rest.TLSClientConfig{
			CAData:   []byte(conf.GetConfig().KubeCA),
			CertData: []byte(conf.GetConfig().KubeCert),
			KeyData:  []byte(conf.GetConfig().KubeData),
		},
	}
	// Create clientset
	clientset, err := kubernetes.NewForConfig(config)
	fmt.Println("NewForConfig", clientset, err)
	if err != nil {
		return nil, errors.New("Error creating Kubernetes client: " + err.Error())
	}
	if clientset == nil {
		return nil, errors.New("Error creating Kubernetes client: clientset is nil")
	}

	return &KubernetesService{
		Set: clientset,
	}, nil
}

func (k *KubernetesService) CreateNamespace(ctx context.Context, ns string) error {
	// Define the namespace
	namespace := &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: ns,
		},
	}
	// Create the namespace
	fmt.Println("Creating namespace...", k.Set)
	if _, err := k.Set.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{}); err != nil {
		return errors.New("Error creating namespace: " + err.Error())
	}
	fmt.Println("Namespace created successfully!")
	return nil
}

func (k *KubernetesService) CreateServiceAccount(ctx context.Context, ns string) error {
	// Create the ServiceAccount object
	serviceAccount := &v1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "sa-" + ns,
			Namespace: ns,
		},
	}
	// Create the ServiceAccount in the specified namespace
	_, err := k.Set.CoreV1().ServiceAccounts(ns).Create(ctx, serviceAccount, metav1.CreateOptions{})
	if err != nil {
		return errors.New("Failed to create ServiceAccount: " + err.Error())
	}
	return nil
}

func (k *KubernetesService) CreateRole(ctx context.Context, ns string, role string, groups [][]string, resources [][]string, verbs [][]string) error {
	// Create the Role object
	if len(groups) != len(resources) || len(resources) != len(verbs) {
		return errors.New("Invalid input: groups, resources, and verbs must have the same length")
	}
	rules := []rbacv1.PolicyRule{}
	for i, group := range groups {
		rules = append(rules, rbacv1.PolicyRule{
			APIGroups: group,
			Resources: resources[i],
			Verbs:     verbs[i],
		})
	}
	r := &rbacv1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name:      role,
			Namespace: ns,
		},
		Rules: rules,
	}
	// Create the Role in the specified namespace
	_, err := k.Set.RbacV1().Roles(ns).Create(ctx, r, metav1.CreateOptions{})
	if err != nil {
		return errors.New("Failed to create Role: " + err.Error())
	}
	return nil
}

func (k *KubernetesService) CreateRoleBinding(ctx context.Context, ns string, roleBinding string, role string) error {
	// Create the RoleBinding object
	rb := &rbacv1.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:      roleBinding,
			Namespace: ns,
		},
		Subjects: []rbacv1.Subject{
			{
				Kind:      "ServiceAccount",
				Name:      "sa-" + ns,
				Namespace: ns,
			},
		},
		RoleRef: rbacv1.RoleRef{
			Kind:     "Role",
			Name:     role,
			APIGroup: "rbac.authorization.k8s.io",
		},
	}
	// Create the RoleBinding in the specified namespace
	_, err := k.Set.RbacV1().RoleBindings(ns).Create(ctx, rb, metav1.CreateOptions{})
	if err != nil {
		return errors.New("Failed to create RoleBinding: " + err.Error())
	}
	return nil
}

func (k *KubernetesService) DeleteNamespace(ctx context.Context, ns string) error {
	// Delete the namespace
	if err := k.Set.CoreV1().Namespaces().Delete(ctx, ns, metav1.DeleteOptions{}); err != nil {
		return errors.New("Error deleting namespace: " + err.Error())
	}
	fmt.Println("Namespace deleted successfully!")
	return nil
}

func (k *KubernetesService) GetToken(ctx context.Context, ns string, duration int) (string, error) {
	// Define TokenRequest (valid for 1 hour)
	d := int64(duration)
	tokenRequest := &authv1.TokenRequest{
		Spec: authv1.TokenRequestSpec{
			ExpirationSeconds: &d, // 1 hour validity
		},
	}
	// Generate the token
	token, err := k.Set.CoreV1().
		ServiceAccounts(ns).
		CreateToken(ctx, "sa-"+ns, tokenRequest, metav1.CreateOptions{})
	if err != nil {
		return "", errors.New("Failed to create token for ServiceAccount: " + err.Error())
	}
	return token.Status.Token, nil
}