package commands

import (
	"context"
	"time"

	pb "code.fbi.h-da.de/cocsn/gosdn/api/proto"
	"code.fbi.h-da.de/cocsn/gosdn/cmd/gosdn-tview/app"
	"github.com/rivo/tview"
	grpc "google.golang.org/grpc"
	//Package google.golang.org/grpc/health is needed to make gRPC Health Check work
	_ "google.golang.org/grpc/health"
	healthpb "google.golang.org/grpc/health/grpc_health_v1"
	"google.golang.org/protobuf/types/known/emptypb"
)

const (
	defaultName = "gosdn-cli"
)

type command struct {
	Name        string
	Description string
	Function    func(conn *grpc.ClientConn) string
}

//CommandList contains the specific goSDN gRPC calls
var CommandList = []command{
	{"hello", "test connection to controller", goSDNSayHello},
	{"testdb", "test all database connections", goSDNTestDB},
	{"tapiGetDevices", "creates devices", TAPIGetEdge},
	{"tapiGetInterfaces", "creates interfaces", TAPIGetEdgeNode},
	{"tapiGetLinks", "creates links between devices", TAPIGetLink},
	{"shutdown", "request controller to shutdown", goSDNShutdown},
}

var serviceConfig = `{
	"loadBalancingPolicy": "round_robin",
	"healthCheckConfig": {
		"serviceName": ""
	}
}`

//Connect creates a new connection to the gRPC server
func Connect(address string) (*grpc.ClientConn, error) {
	options := []grpc.DialOption{
		grpc.WithInsecure(),
		grpc.WithBlock(),
		grpc.WithDefaultServiceConfig(serviceConfig),
		grpc.WithTimeout(5 * time.Second),
	}

	return grpc.Dial(address, options...)
}

//GoSDNLogStream creates a continuous gRPC stream to recieve goSDN logs
func GoSDNLogStream(app *app.App, conn *grpc.ClientConn, tv *tview.TextView) error {
	var streamError error
	c := pb.NewGrpcCliClient(conn)
	stream, err := c.CreateLogStream(context.Background(), &emptypb.Empty{})
	if err != nil {
		return err
	}

	go func(stream pb.GrpcCli_CreateLogStreamClient) {
		for {
			msg, err := stream.Recv()
			if err != nil {
				streamError = err

				go func() {
					ticker := time.NewTicker(5 * time.Second)
					for range ticker.C {
						if err := GoSDNLogStream(app, conn, tv); err == nil {
							ticker.Stop()
							return
						}
					}
				}()

				break
			}
			response := []byte(msg.Log)
			app.QueueUpdateDraw(func() {
				tv.Write(response)
			})
		}
	}(stream)

	return streamError
}

func goSDNSayHello(conn *grpc.ClientConn) string {
	c := pb.NewGrpcCliClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
	if err != nil {
		return err.Error()
	}

	return r.GetMessage()
}

//WatchHealth continuous gRPC Health Check stream to recieve health changes
func WatchHealth(service string, app *app.App, conn *grpc.ClientConn, tv *tview.TextView) error {
	var streamError error

	c := healthpb.NewHealthClient(conn)
	stream, err := c.Watch(context.Background(), &healthpb.HealthCheckRequest{Service: service})
	if err != nil {
		app.QueueUpdateDraw(func() {
			tv.Clear()
			tv.SetText(err.Error())
		})
		return err
	}

	go func(stream healthpb.Health_WatchClient) {
		for {
			msg, err := stream.Recv()
			if err != nil {
				streamError = err
				go func() {
					ticker := time.NewTicker(5 * time.Second)
					for range ticker.C {
						if err := WatchHealth(service, app, conn, tv); err == nil {
							ticker.Stop()
							return
						}
					}
				}()

				break
			}

			app.QueueUpdateDraw(func() {
				tv.Clear()
				tv.SetText(msg.GetStatus().String())
			})

		}
	}(stream)

	return streamError
}

func goSDNShutdown(conn *grpc.ClientConn) string {

	c := pb.NewGrpcCliClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.Shutdown(ctx, &pb.ShutdownRequest{Name: name})
	if err != nil {
		return err.Error()
	}
	return r.GetMessage()
}

func goSDNTestDB(conn *grpc.ClientConn) string {
	// TODO: fill with code and also see if grpc interface has this stub implemented.
	return "not implemented yet"
}

// TAPIGetEdge triggers the GetEdge function of the Ciena
// flavoured TAPI client
func TAPIGetEdge(conn *grpc.ClientConn) string {

	c := pb.NewGrpcCliClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.TAPIGetEdge(ctx, &pb.TAPIRequest{Name: name})
	if err != nil {
		return err.Error()
	}
	return r.GetMessage()
}

// TAPIGetEdgeNode triggers the GetEdgeNode function of the Ciena
// flavoured TAPI client
func TAPIGetEdgeNode(conn *grpc.ClientConn) string {
	c := pb.NewGrpcCliClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.TAPIGetEdgeNode(ctx, &pb.TAPIRequest{Name: name})
	if err != nil {
		return err.Error()
	}
	return r.GetMessage()
}

// TAPIGetLink triggers the GetLink function of the Ciena
// flavoured TAPI client
func TAPIGetLink(conn *grpc.ClientConn) string {

	c := pb.NewGrpcCliClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.TAPIGetLink(ctx, &pb.TAPIRequest{Name: name})
	if err != nil {
		return err.Error()
	}
	return r.GetMessage()
}