package main

import (
	"context"
	_ "encoding/json"
	"fmt"
	"net/url"

	"github.com/aws/aws-lambda-go/lambda"
	restclient "k8s.io/client-go/rest"

	"github.com/tidbcloud/eks-bootstrap/pkg/coredns"
	"github.com/tidbcloud/eks-bootstrap/pkg/frpc"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/clientcmd/api"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
	scheme = runtime.NewScheme()
)

func init() {
	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
}

func constructClientConfigForEKS(eksARN, endpoint, tokenStr string) (*restclient.Config, error) {
	cfg := api.NewConfig()
	cfg.APIVersion, cfg.Kind, cfg.CurrentContext = "v1", "Config", eksARN

	cluster, auth, cfgContext := api.NewCluster(), api.NewAuthInfo(), api.NewContext()
	cfg.Clusters[eksARN] = cluster
	cfg.AuthInfos[eksARN] = auth
	cfg.Contexts[eksARN] = cfgContext

	// config cluster
	cluster.Server = endpoint
	cluster.InsecureSkipTLSVerify = true

	// config authinfo
	auth.Token = tokenStr

	// config context
	cfgContext.Cluster = eksARN
	cfgContext.AuthInfo = eksARN

	return clientcmd.NewDefaultClientConfig(*cfg, nil).ClientConfig()
}

func constructEKSClient(eksARN, endpoint, tokenStr string) (client.Client, error) {
	restCfg, err := constructClientConfigForEKS(eksARN, endpoint, tokenStr)
	if err != nil {
		return nil, err
	}
	cli, err := client.New(restCfg, client.Options{Scheme: scheme})
	if err != nil {
		return nil, err
	}

	return cli, nil
}

func deployFrpc(ctx context.Context, cli client.Client, frpcImage, frpsSvcDomain, endpoint string, res *BootstrapResponse) error {
	parsedURL, err := url.Parse(endpoint)
	if err != nil {
		return err
	}
	f := frpc.NewFrpc()
	// Deploy frpc deployment
	err = f.FrpcDeployment(ctx, cli, frpcImage)
	if err != nil {
		return err
	}
	out, err := f.DeploymentData(ctx, cli)
	if err != nil {
		return err
	}
	res.FrpcDeployment = out

	// Deploy frpc configmap
	err = f.FrpcConfigMap(ctx, cli, parsedURL.Host, frpsSvcDomain)
	if err != nil {
		return err
	}
	out, err = f.ConfigMapData(ctx, cli)
	if err != nil {
		return err
	}
	res.FrpcConfigMap = out

	return nil
}

func updateCoreDNS(ctx context.Context, cli client.Client, serviceDNS map[string]string, res *BootstrapResponse) error {
	c := coredns.NewCoreDNSCM()
	if err := c.UpdateCoreDNS(ctx, cli, serviceDNS); err != nil {
		return err
	}
	out, err := c.CoreDNSData(ctx, cli)
	if err != nil {
		return err
	}
	res.CoreDNSData = out
	return nil
}

func handlerBootstrap(ctx context.Context, info ClusterInfo, req BootstrapRequest) (BootstrapResponse, error) {
	var result BootstrapResponse

	// Construct clientset for EKS
	cli, err := constructEKSClient(info.EksArn, info.Endpoint, info.Token)
	if err != nil {
		return result, err
	}

	if err := deployFrpc(ctx, cli, req.FrpcImage, req.FrpsEndpointSvc, info.Endpoint, &result); err != nil {
		return result, err
	}

	if err := updateCoreDNS(ctx, cli, req.ServiceDNS, &result); err != nil {
		return result, err
	}

	return result, nil
}

// checkStatus returns true if all frpc pods are ready.
func checkStatus(ctx context.Context, info ClusterInfo) (bool, error) {
	cli, err := constructEKSClient(info.EksArn, info.Endpoint, info.Token)
	if err != nil {
		return false, err
	}

	f := frpc.NewFrpc()
	if ready, err := f.FrpcReady(ctx, cli); err != nil {
		return false, err
	} else if !ready {
		return false, nil
	}
	return true, nil
}

func Handler(ctx context.Context, req Request) (Response, error) {
	// check cluster info
	if req.Token == "" {
		return Response{}, fmt.Errorf("Missing token")
	}
	if req.EksArn == "" {
		return Response{}, fmt.Errorf("Missing EKS ARN")
	}
	if req.Endpoint == "" {
		return Response{}, fmt.Errorf("Missing EKS endpoint")
	}
	var result Response
	if req.BootstrapRequest != nil {
		bootstrapResp, err := handlerBootstrap(ctx, req.ClusterInfo, *req.BootstrapRequest)
		if err != nil {
			return Response{BootstrapResponse: bootstrapResp}, err
		}
		result.BootstrapResponse = bootstrapResp
	}

	if req.Status {
		ready, err := checkStatus(ctx, req.ClusterInfo)
		if err != nil {
			return result, err
		}
		result.Ready = &ready
	}
	return result, nil
}

func main() {
	lambda.Start(Handler)
}
