package server

import (
	"crypto/tls"
	"errors"
	"net"
	"net/http"
	"net/http/httputil"
	"time"

	"git.xeserv.us/xena/route/database"
	"git.xeserv.us/xena/route/lib/tun2"
	proto "git.xeserv.us/xena/route/proto"
	"github.com/mtneug/pkg/ulid"
	"golang.org/x/crypto/acme/autocert"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// RPC constants
const (
	RPCPort uint16 = 39453
)

// Server is the main server type
type Server struct {
	cfg *Config
	db  database.Storage
	ts  *tun2.Server

	*autocert.Manager
}

// Config configures Server
type Config struct {
	BoltDBPath string `env:"BOLTDB_PATH,required"`

	WebAddr        string `env:"WEB_ADDR,required"`
	SSLAddr        string `env:"SSL_ADDR,required"`
	BackendTCPAddr string `env:"BACKEND_TCP_ADDR,required"`
	BackendKCPAddr string `env:"BACKEND_KCP_ADDR,required"`
	GRPCAddr       string `env:"GRPC_ADDR,required"`

	DomainSuffix string `env:"DOMAIN_SUFFIX,required"`
	ACMEEmail    string `env:"ACME_EMAIL,required"`
	CertKey      *[32]byte
}

// New creates a new Server
func New(cfg Config) (*Server, error) {
	if cfg.CertKey == nil {
		return nil, errors.New("no cert decryption key, can't do anything")
	}

	db, err := database.NewBoltStorage(cfg.BoltDBPath, cfg.CertKey)
	if err != nil {
		return nil, err
	}

	m := &autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		Cache:      database.Cache(db),
		HostPolicy: nil,
		Email:      cfg.ACMEEmail,
	}

	tcfg := &tun2.ServerConfig{
		TCPAddr: cfg.BackendTCPAddr,
		KCPAddr: cfg.BackendKCPAddr,
		TLSConfig: &tls.Config{
			GetCertificate: m.GetCertificate,
		},
		Storage: &storageWrapper{
			Storage: db,
		},
	}

	ts, err := tun2.NewServer(tcfg)
	if err != nil {
		return nil, err
	}
	s := &Server{
		cfg: &cfg,
		db:  db,
		ts:  ts,

		Manager: m,
	}

	s.ts = ts
	go ts.ListenAndServe()

	gs := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{
		GetCertificate:     m.GetCertificate,
		InsecureSkipVerify: true,
	})))

	proto.RegisterRoutesServer(gs, &Route{Server: s})
	proto.RegisterTokensServer(gs, &Token{Server: s})

	l, err := net.Listen("tcp", cfg.GRPCAddr)
	if err != nil {
		return nil, err
	}

	go gs.Serve(l)

	return s, nil
}

// Director removes headers that are typically stripped off at request ingress.
func (s *Server) Director(r *http.Request) {
	r.Header.Del("X-Forwarded-For")
	r.Header.Del("X-Client-Ip")
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Header.Get("X-Tor2web") != "" {
		http.Error(w, "tor2web proxy use is not allowed", 400)
		return
	}

	host, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	r.Header.Set("X-Remote-Ip", host)
	r.Header.Set("X-Request-Ingress", time.Now().Format(time.RFC3339))

	rid := ulid.New().String()
	r.Header.Set("X-Request-Id", rid)
	w.Header().Set("X-Request-Id", rid)

	// http://www.gnuterrypratchett.com/
	w.Header().Set("X-Clacks-Overhead", "GNU Ashlynn")

	rp := &httputil.ReverseProxy{
		Director:      s.Director,
		Transport:     s.ts,
		FlushInterval: 1 * time.Second,
	}

	rp.ServeHTTP(w, r)
}
