package api

import (
	"fmt"
	"google.golang.org/protobuf/encoding/protojson"
	"net/http"
	"strings"

	"github.com/asaskevich/govalidator"
	"github.com/cad/ovpm/api/pb"
	"github.com/cad/ovpm/bundle"
	assetfs "github.com/elazarl/go-bindata-assetfs"
	"github.com/go-openapi/runtime/middleware"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/sirupsen/logrus"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// NewRESTServer returns a new REST server.
func NewRESTServer(grpcPort string) (http.Handler, context.CancelFunc, error) {
	mux := http.NewServeMux()
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	if !govalidator.IsNumeric(grpcPort) {
		return nil, cancel, fmt.Errorf("grpcPort should be numeric")
	}
	endPoint := fmt.Sprintf("localhost:%s", grpcPort)
	ctx = NewOriginTypeContext(ctx, OriginTypeREST)
	gmux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
		MarshalOptions: protojson.MarshalOptions{
			UseProtoNames:   true,
			EmitUnpopulated: true,
		},
		UnmarshalOptions: protojson.UnmarshalOptions{
			DiscardUnknown: true,
		},
	}))
	opts := []grpc.DialOption{grpc.WithInsecure()}
	err := pb.RegisterVPNServiceHandlerFromEndpoint(ctx, gmux, endPoint, opts)
	if err != nil {
		return nil, cancel, err
	}

	err = pb.RegisterUserServiceHandlerFromEndpoint(ctx, gmux, endPoint, opts)
	if err != nil {
		return nil, cancel, err
	}

	err = pb.RegisterNetworkServiceHandlerFromEndpoint(ctx, gmux, endPoint, opts)
	if err != nil {
		return nil, cancel, err
	}

	err = pb.RegisterAuthServiceHandlerFromEndpoint(ctx, gmux, endPoint, opts)
	if err != nil {
		return nil, cancel, err
	}

	mux.HandleFunc("/api/specs/", specsHandler)
	mware := middleware.Redoc(middleware.RedocOpts{
		BasePath: "/api/docs/",
		SpecURL:  "/api/specs/user.swagger.json",
		Path:     "user",
	}, gmux)
	mware = middleware.Redoc(middleware.RedocOpts{
		BasePath: "/api/docs/",
		SpecURL:  "/api/specs/vpn.swagger.json",
		Path:     "vpn",
	}, mware)
	mware = middleware.Redoc(middleware.RedocOpts{
		BasePath: "/api/docs/",
		SpecURL:  "/api/specs/network.swagger.json",
		Path:     "network",
	}, mware)
	mware = middleware.Redoc(middleware.RedocOpts{
		BasePath: "/api/docs/",
		SpecURL:  "/api/specs/auth.swagger.json",
		Path:     "auth",
	}, mware)
	mux.Handle("/api/", mware)
	mux.Handle("/", http.FileServer(
		&assetfs.AssetFS{Asset: bundle.Asset, AssetDir: bundle.AssetDir, Prefix: "bundle"}))

	return allowCORS(mux), cancel, nil
}

func specsHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	switch r.URL.Path {
	case "/api/specs/user.swagger.json":
		userData, err := bundle.Asset("bundle/user.swagger.json")
		if err != nil {
			logrus.Warn(err)
		}
		w.Write(userData)

	case "/api/specs/network.swagger.json":
		networkData, err := bundle.Asset("bundle/network.swagger.json")
		if err != nil {
			logrus.Warn(err)
		}
		w.Write(networkData)
	case "/api/specs/vpn.swagger.json":
		vpnData, err := bundle.Asset("bundle/vpn.swagger.json")
		if err != nil {
			logrus.Warn(err)
		}
		w.Write(vpnData)
	case "/api/specs/auth.swagger.json":
		vpnData, err := bundle.Asset("bundle/auth.swagger.json")
		if err != nil {
			logrus.Warn(err)
		}
		w.Write(vpnData)
	}
}

func webuiHandler(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/bundle.js":
		userData, err := bundle.Asset("bundle/bundle.js")
		if err != nil {
			logrus.Warn(err)
		}
		w.Header().Set("Content-Type", "application/javascript")
		w.Write(userData)
	case "/js/mui.min.js":
		userData, err := bundle.Asset("bundle/js/mui.min.js")
		if err != nil {
			logrus.Warn(err)
		}
		w.Header().Set("Content-Type", "application/javascript")
		w.Write(userData)
	case "/css/bootstrap.min.css":
		userData, err := bundle.Asset("bundle/css/bootstrap.min.css")
		if err != nil {
			logrus.Warn(err)
		}
		w.Header().Set("Content-Type", "text/css")
		w.Write(userData)
	case "/css/mui.min.css":
		userData, err := bundle.Asset("bundle/css/mui.min.css")
		if err != nil {
			logrus.Warn(err)
		}
		w.Header().Set("Content-Type", "text/css")
		w.Write(userData)
	case "/fonts/glyphicons-halflings-regular.woff":
		userData, err := bundle.Asset("bundle/glyphicons-halflings-regular.woff")
		if err != nil {
			logrus.Warn(err)
		}
		w.Header().Set("Content-Type", "application/font-woff")
		w.Write(userData)

	default:
		networkData, err := bundle.Asset("bundle/index.html")
		if err != nil {
			logrus.Warn(err)
		}
		w.Write(networkData)
	}
}

func preflightHandler(w http.ResponseWriter, r *http.Request) {
	headers := []string{"Content-Type", "Accept", "Authorization"}
	w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ","))
	methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE"}
	w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ","))
	w.Header().Set("Access-Control-Expose-Headers", "Access-Control-Allow-Origin")
	logrus.Debugf("rest: preflight request for %s", r.URL.Path)
	return
}

func allowCORS(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if origin := r.Header.Get("Origin"); origin != "" {
			w.Header().Set("Access-Control-Allow-Origin", origin)
			if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" {
				preflightHandler(w, r)
				return
			}
		}
		h.ServeHTTP(w, r)
	})
}
