package main

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"git.xeserv.us/xena/route/database"
	"git.xeserv.us/xena/route/lib/routecrypto"
	proto "git.xeserv.us/xena/route/proto"
	"github.com/Xe/ln"
	"github.com/Xe/uuid"
	jwtcreds "github.com/Xe/x/tools/svc/credentials/jwt"
	"github.com/dickeyxxx/netrc"
	"github.com/olekukonko/tablewriter"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"gopkg.in/alecthomas/kingpin.v2"
)

var allScopes = []string{
	"token:get", "token:getall", "token:put", "token:delete", "token:deactivate",
	"route:get", "route:getall", "route:put", "route:delete",
	"connect",
}

var whoami string

func userHomeDir() string {
	if runtime.GOOS == "windows" {
		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
		if home == "" {
			home = os.Getenv("USERPROFILE")
		}
		return home
	}
	return os.Getenv("HOME")
}

var (
	app        = kingpin.New("route", "An interface to routed")
	grpcServer = app.Flag("routed-addr", "routed grpc address").Default("127.0.0.1:7268").String()
	netrcPath  = app.Flag("netrc", "netrc path").Default(filepath.Join(userHomeDir(), ".netrc")).String()

	generateKey = app.Command("generate-key", "generate SSL cert crypto key")

	routes = app.Command("route", "route management")

	routesCreate       = routes.Command("create", "create a new route")
	routesCreateDomain = routesCreate.Flag("domain", "domain for the route (if not given one will be generated for you)").String()

	routesInspect       = routes.Command("inspect", "inspect one route")
	routesInspectDomain = routesInspect.Flag("domain", "domain to inspect").Required().String()

	routesList = routes.Command("list", "list all routes owned by you")

	routesRm   = routes.Command("rm", "remove a route")
	routesRmID = routesRm.Flag("id", "route ID to remove").Required().String()

	token = app.Command("token", "token management")

	tokenCreate       = token.Command("create", "create a new token")
	tokenCreateScopes = tokenCreate.Flag("scope", "token scopes").Default(allScopes...).Strings()

	tokenGenerate             = token.Command("generate-root", "generate a root token")
	tokenGenerateKey          = tokenGenerate.Flag("key", "token crypto key").Required().String()
	tokenGenerateScopes       = tokenGenerate.Flag("scopes", "token scopes").Default(allScopes...).Strings()
	tokenGenerateUsername     = tokenGenerate.Flag("username", "token username").Required().String()
	tokenGenerateDatabasePath = tokenGenerate.Flag("db", "database file to add the root token to").Required().String()

	tokenInspect   = token.Command("inspect", "inspect a token")
	tokenInspectID = tokenInspect.Arg("token-id", "token id").Required().String()

	tokenList            = token.Command("list", "list all tokens belonging to you")
	tokenListDeactivated = tokenList.Flag("deactivated", "list deactivated tokens?").Default("false").Bool()

	tokenRm     = token.Command("rm", "remove a token")
	tokenRmHard = tokenRm.Flag("hard", "hard-delete the token instead of deactivating it").Default("false").Bool()
	tokenRmID   = tokenRm.Flag("id", "token id").Required().String()

	// testServer     = app.Command("test-server", "spawn a simple HTTP test server on a TCP address")
	// testServerAddr = testServer.Flag("addr", "TCP address").Default(":9090").String()

	// connect = app.Command("connect", "connect to the backend server and relay client HTTP traffic")
)

func main() {
	cmdline := kingpin.MustParse(app.Parse(os.Args[1:]))

	n, err := netrc.Parse(*netrcPath)
	if err != nil {
		ln.Fatal(ln.F{"err": err, "action": "netrc.Parse"})
	}
	_ = n

	switch cmdline {
	case "generate-key":
		key, err := routecrypto.GenerateKey()
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "routecrypto.GenerateKey"})
		}

		fmt.Println("Your key is:", routecrypto.ShowKey(key))
	case "token generate-root":
		key, err := routecrypto.ParseKey(*tokenGenerateKey)
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "routecrypto.ParseKey"})
		}
		db, err := database.NewBoltStorage(*tokenGenerateDatabasePath, key)
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "database.NewBoltStorage"})
		}

		tBody := uuid.New()

		_, err = db.PutToken(context.Background(), tBody, *tokenGenerateUsername, *tokenGenerateScopes)
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "db.PutToken"})
		}
		defer db.Close()

		fmt.Println("Your token is:", tBody)

		n.AddMachine(*grpcServer, *tokenGenerateUsername, tBody)
		err = n.Save()
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "n.Save"})
		}

		return
	}

	m := n.Machine(*grpcServer)

	connCreds := credentials.NewTLS(&tls.Config{
		InsecureSkipVerify: true,
	})
	creds := jwtcreds.NewFromToken(m.Get("password"))
	conn, err := grpc.Dial(*grpcServer,
		grpc.WithTransportCredentials(connCreds),
		grpc.WithPerRPCCredentials(creds))
	if err != nil {
		ln.Fatal(ln.F{"err": err, "action": "grpc.Dial"})
	}

	rc := proto.NewRoutesClient(conn)
	tc := proto.NewTokensClient(conn)

	_ = rc
	_ = tc

	switch cmdline {
	case "route create":
		idr, err := rc.Put(context.Background(), &proto.Route{Host: *routesCreateDomain})
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "rc.Put"})
		}

		fmt.Println(idr.Id)

	case "route inspect":
		r, err := rc.Get(context.Background(), &proto.GetRouteRequest{
			Host: *routesCreateDomain,
		})
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "rc.Get"})
		}

		json.NewEncoder(os.Stdout).Encode(r)
		fmt.Println()

		return

	case "route list":
		rts, err := rc.GetAll(context.Background(), &proto.Nil{})
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "rc.GetAll"})
		}

		table := tablewriter.NewWriter(os.Stdout)
		table.SetHeader([]string{"ID", "Host"})

		for _, r := range rts.Routes {
			table.Append([]string{r.Id, r.Host})
		}

		return

	case "route rm":
		_, err := rc.Delete(context.Background(), &proto.Route{Id: *routesRmID})
		if err != nil {
			ln.Fatal(ln.F{"err": err, "action": "rc.Delete"})
		}
	}
}
