package controllers

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"oc-datacenter/conf"
	"oc-datacenter/infrastructure"
	"oc-datacenter/models"
	"slices"
	"time"

	oclib "cloud.o-forge.io/core/oc-lib"

	beego "github.com/beego/beego/v2/server/web"
	jwt "github.com/golang-jwt/jwt/v5"
	"gopkg.in/yaml.v2"
	v1 "k8s.io/api/core/v1"
)

type KubeInfo struct {
	Url 			*string
	KubeCA   		*string
	KubeCert 		*string
	KubeKey 		*string
}

type RemoteKubeconfig struct {
	Data	*string
}

type KubeUser struct {
	Name 	string
	User 	struct {
		Token 	string
	}
}

type KubeconfigToken struct {
	ApiVersion		string		`yaml:"apiVersion"`
	Kind			string		`yaml:"kind"`
	Preferences		string		`yaml:"preferences"`
	CurrentContext	string		`yaml:"current-context"`
	Clusters []struct{			
		Cluster struct{	
			CA		string		`yaml:"certificate-authority-data"`
			Server	string		`yaml:"server"`
		
		}						`yaml:"cluster"`
		Name		string		`yaml:"name"`
	} 							`yaml:"clusters"`
	Contexts []struct{
		Context	struct{
			Cluster	string		`yaml:"cluster"`
			User 	string		`yaml:"user"`
		}						`yaml:"context"`
		Name 		string		`yaml:"name"`
	}							`yaml:"contexts"`
	Users	[]struct{
		Name 		string		`yaml:"name"`
		User struct {
			Token	string		`yaml:"token"`
		}						`yaml:"user"`
	}							`yaml:"users"`
}



// Operations about the admiralty objects of the datacenter
type AdmiraltyController struct {
	beego.Controller
}

// @Title GetAllTargets
// @Description find all Admiralty Target
// @Success 200 
// @router /targets [get]
func (c *AdmiraltyController) GetAllTargets() {
	serv, err := infrastructure.NewService()
	if err != nil {
		// change code to 500
		HandleControllerErrors(c.Controller,500,&err,nil)
		// c.Ctx.Output.SetStatus(500)
		// c.ServeJSON()
		// c.Data["json"] = map[string]string{"error": err.Error()}
		return
	}
	
	res, err := serv.GetTargets(c.Ctx.Request.Context())
	c.Data["json"] = res
	c.ServeJSON()
}

// @Title GetOneTarget
// @Description find one Admiralty Target
// @Param id path string true "the name of the target to get"
// @Success 200 
// @router /targets/:execution [get]
func (c *AdmiraltyController) GetOneTarget() {
	id := c.Ctx.Input.Param(":execution")
	serv, err := infrastructure.NewService()
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.ServeJSON()
		c.Data["json"] = map[string]string{"error": err.Error()}
		return
	}
	
	res, err := serv.GetTargets(c.Ctx.Request.Context())
	id = "target-"+id
	found := slices.Contains(res,id)
	if !found {
		c.Ctx.Output.SetStatus(404)
		c.ServeJSON()
	}

	c.Data["json"] = id
	c.ServeJSON()
}

// @Title CreateSource
// @Description Create an Admiralty Source on remote cluster
// @Param execution path 	string 		true	"execution id of the workflow"
// @Success 201 
// @router /source/:execution [post]
func (c *AdmiraltyController) CreateSource() {

	execution := c.Ctx.Input.Param(":execution")
	fmt.Println("execution :: ", execution)
	fmt.Println("input :: ", c.Ctx.Input)
	serv, err := infrastructure.NewKubernetesService()
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.ServeJSON()
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	res, err := serv.CreateAdmiraltySource(c.Ctx.Request.Context(),execution)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	
	// TODO : Return a description of the created resource
	var respData map[string]interface{}
	err = json.Unmarshal(res,&respData)
	
	c.Ctx.Output.SetStatus(201)
	c.Data["json"] = respData
	c.ServeJSON()
	
}

// @Title CreateAdmiraltyTarget
// @Description Create an Admiralty Target in the namespace associated to the executionID
// @Param execution path 	string 		true	"execution id of the workflow"
// @Success 201
// @router /target/:execution [post]
func (c *AdmiraltyController) CreateAdmiraltyTarget(){
	var data map[string]interface{}
	
	execution := c.Ctx.Input.Param(":execution")


	serv, err := infrastructure.NewService()
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	resp, err := serv.CreateAdmiraltyTarget(c.Ctx.Request.Context(),execution)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	if resp == nil {
		fmt.Println("Error while trying to create Admiralty target")
		fmt.Println(resp)
		fmt.Println(err)
		c.Ctx.Output.SetStatus(401)
		c.Data["json"] = map[string]string{"error" : "Could not perform the action" }
		c.ServeJSON()
		return
	}

	err = json.Unmarshal(resp,&data)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.ServeJSON()
		c.Data["json"] = map[string]string{"error": err.Error()}
		return
	}
	c.Ctx.Output.SetStatus(201)
	c.Data["json"] = data
	c.ServeJSON()

}

// @Title GetKubeSecret
// @Description Retrieve the secret created from a Kubeconfig that will be associated to an Admiralty Target

// @Param execution path 	string 		true	"execution id of the workflow"
// @Success 200 
// @router /secret/:execution [get]
func(c *AdmiraltyController) GetKubeSecret() {
	var data map[string]interface{} 
	
	execution := c.Ctx.Input.Param(":execution")

	serv, err := infrastructure.NewService()
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(),execution)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	if resp == nil {
		c.Ctx.Output.SetStatus(404)
		c.ServeJSON()
		return
	}
	
	err = json.Unmarshal(resp,&data)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.ServeJSON()
		c.Data["json"] = map[string]string{"error": err.Error()}
		return
	}

	c.Data["json"] = data
	c.ServeJSON()
}


// @Title CreateKubeSecret
// @Description Creat a secret from a Kubeconfig that will be associated to an Admiralty Target

// @Param execution path 	string 		true	"execution id of the workflow"
// @Param kubeconfig 	body	controllers.RemoteKubeconfig 		true 	"Kubeconfig to use when creating secret"
// @Success 201 
// @router /secret/:execution [post]
func (c *AdmiraltyController) CreateKubeSecret() {
	var kubeconfig 	RemoteKubeconfig
	var respData	map[string]interface{}

	data := c.Ctx.Input.CopyBody(100000)

	err := json.Unmarshal(data, &kubeconfig)
	if err != nil {
		fmt.Println("Error when retrieving the data for kubeconfig from request")
		fmt.Println(err)
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	
	execution := c.Ctx.Input.Param(":execution")


	serv, err := infrastructure.NewService()
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	resp, err := serv.CreateKubeconfigSecret(c.Ctx.Request.Context(),*kubeconfig.Data,execution)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	err = json.Unmarshal(resp,&respData)
	c.Ctx.Output.SetStatus(201)
	c.Data["json"] = respData
	c.ServeJSON()

}

// @name GetAdmiraltyNodes
// @description	Allows user to test if an admiralty connection has already been established : Target and valid Secret set up on the local host  and Source set up on remote host
// @Param execution path 	string 		true	"execution id of the workflow"
// @Success 200 
// @router /node/:execution [get]
func (c *AdmiraltyController) GetNodeReady(){
	var secret v1.Secret
	execution := c.Ctx.Input.Param(":execution")


	serv, err := infrastructure.NewService()
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	node, err := serv.GetOneNode(c.Ctx.Request.Context(),execution)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	if node == nil {
		c.Ctx.Output.SetStatus(404)
		c.Data["json"] = map[string]string{
			"node" : "the node for " + execution + " can't be found, make sure both target and source resources are set up on local and remote hosts",
		}
		c.ServeJSON()
		return
	}



	resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(),execution)
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	if resp == nil {
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": "Nodes was up but the secret can't be found"}
		c.ServeJSON()
		return
	}

	// Extract JWT token RS265 encoded
	var editedKubeconfig map[string]interface{}
	json.Unmarshal(resp,&secret)
	byteEditedKubeconfig := secret.Data["config"]
	err = yaml.Unmarshal(byteEditedKubeconfig,&editedKubeconfig)
	// err = json.Unmarshal(byteEditedKubeconfig,&editedKubeconfig)
	if err != nil {
		fmt.Println("Error while retrieving the kubeconfig from secret-",execution)
		fmt.Println(err)
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = err
		c.ServeJSON()
		return
	}
	
	token, err := retrieveTokenFromKonfig(editedKubeconfig)
	if err != nil {
		fmt.Println("Error while trying to retrieve token for kubeconfing")
		fmt.Println(err)
		HandleControllerErrors(c.Controller,500,&err,nil)
	}
	
	// Decode token
	isExpired, err := isTokenExpired(token)
	if err != nil {
		fmt.Println("Error veryfing token's expiration")
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = err
		c.ServeJSON()
	}

	if *isExpired {
		c.Data["json"] = map[string]interface{}{
			"token" : "token in the secret is expired and must be regenerated",
			"node": node,
		}
		c.Ctx.Output.SetStatus(410)
		c.ServeJSON()
	}

	c.Data["json"] = map[string]interface{}{"node": node,"token": true}
	c.ServeJSON()
	
}

func retrieveTokenFromKonfig(editedKubeconfig map[string]interface{}) (string,error) {
	var kubeUsers []KubeUser
	b, err := yaml.Marshal(editedKubeconfig["users"])
	if err != nil {
		fmt.Println("Error while retrieving the users attribute from the Kubeconfig")
		fmt.Println(err)
		return "", err
	}
	err = yaml.Unmarshal(b,&kubeUsers)
	if err != nil {
		fmt.Println("Error while unmarshalling users attribute from kubeconfig")
		fmt.Println(err)
		return "", nil
	}
	fmt.Println(kubeUsers)
	token := kubeUsers[0].User.Token

	return token, nil
}

func isTokenExpired(token string) (*bool, error){
	logger := oclib.GetLogger()

	t, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
	if err != nil {
		fmt.Println("couldn't decode token")
		return nil, err
	}

	expiration, err := t.Claims.GetExpirationTime()
	if err != nil {
		fmt.Println("Error while checking token's expiration time")
		return nil, err
	}

	logger.Debug().Msg("Expiration date : " + expiration.UTC().Format("2006-01-02T15:04:05"))
	logger.Debug().Msg(fmt.Sprint("Now : ", time.Now().Unix()))
	logger.Debug().Msg(fmt.Sprint("Token : ", expiration.Unix()))


	expired := expiration.Unix() < time.Now().Unix()

	return &expired, nil
} 

// @name Get Admiralty Kubeconfig
// @description	Retrieve a kubeconfig from the host with the token to authenticate as the SA from the namespace identified with execution id

// @Param execution path 	string 		true	"execution id of the workflow"
// @Success 200 
// @router /kubeconfig/:execution [get]
func (c *AdmiraltyController) GetAdmiraltyKubeconfig() {
	
	execution := c.Ctx.Input.Param(":execution")


	serv, err := infrastructure.NewService()
	if err != nil {
		// change code to 500
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	generatedToken, err := serv.GenerateToken(c.Ctx.Request.Context(),execution,3600)
	if err != nil {
		fmt.Println("Couldn't generate a token for ns-", execution)
		fmt.Println(err)
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	kubeconfig, err := NewHostKubeWithToken(generatedToken)
	if err != nil {
		fmt.Println("Could not retrieve the Kubeconfig edited with token")
		fmt.Println(err)
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}

	b, err := json.Marshal(kubeconfig)
	if err != nil {
		fmt.Println("Error while marshalling kubeconfig")
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
	}

	encodedKubeconfig := base64.StdEncoding.EncodeToString(b)
	c.Data["json"] = map[string]string{
		"data": encodedKubeconfig,
	}
	
	c.ServeJSON()
}


func NewHostKubeWithToken(token string) (*models.KubeConfigValue, error){
	if len(token) == 0 {
		return nil, fmt.Errorf("you didn't provide a token to be inserted in the Kubeconfig")
	}

	encodedCA := base64.StdEncoding.EncodeToString([]byte(conf.GetConfig().KubeCA))

	hostKube := models.KubeConfigValue{
		APIVersion: "v1",
		CurrentContext: "default",
		Kind: "Config",
		Preferences: struct{}{},
		Clusters: []models.KubeconfigNamedCluster{
			{
				Name: "default",
				Cluster: models.KubeconfigCluster{
					Server: "https://" + conf.GetConfig().KubeHost + ":6443",
					CertificateAuthorityData: encodedCA,
				},
			},
		},
		Contexts: []models.KubeconfigNamedContext{
			{
				Name: "default",
				Context: models.KubeconfigContext{
					Cluster: "default",
					User: "default",
				},
			},
		},
		Users: []models.KubeconfigUser{
			models.KubeconfigUser{
				Name: "default",
				User: models.KubeconfigUserKeyPair{
					Token: token,
				},
			},
		},
	}

	return &hostKube, nil
}