package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"golang.org/x/crypto/acme/autocert"
	"src.rybka.ca/pkg/api"
	"src.rybka.ca/pkg/auth"
	"src.rybka.ca/pkg/canaconnect"
	"src.rybka.ca/pkg/dl"
	"src.rybka.ca/pkg/playground"
	"src.rybka.ca/pkg/rosary"
	"src.rybka.ca/pkg/rybkaos"
	"src.rybka.ca/pkg/sms"
	"src.rybka.ca/pkg/storage"
	"src.rybka.ca/pkg/twilio"
	"src.rybka.ca/pkg/util"
)

func main() {
	internalAPIKey := os.Getenv("INTERNAL_API_KEY")
	if internalAPIKey == "" {
		log.Fatal("INTERNAL_API_KEY not set")
	}
	twilioAccountSID := os.Getenv("TWILIO_ACCOUNT_SID")
	if twilioAccountSID == "" {
		log.Fatal("TWILIO_ACCOUNT_SID not set")
	}
	twilioAuthToken := os.Getenv("TWILIO_AUTH_TOKEN")
	if twilioAuthToken == "" {
		log.Fatal("TWILIO_AUTH_TOKEN not set")
	}
	twilioPhoneNumber := os.Getenv("TWILIO_PHONE_NUMBER")
	if twilioPhoneNumber == "" {
		log.Fatal("TWILIO_PHONE_NUMBER not set")
	}
	relayPhoneNumber := os.Getenv("RELAY_PHONE_NUMBER")
	if relayPhoneNumber == "" {
		log.Fatal("RELAY_PHONE_NUMBER not set")
	}
	smsClient := &sms.Client{
		TwilioClient: &twilio.Client{
			AccountSID:  twilioAccountSID,
			AuthToken:   twilioAuthToken,
			PhoneNumber: twilioPhoneNumber,
		},
		RelayPhoneNumber: relayPhoneNumber,
	}
	apiServer := &api.Server{
		Storage: &storage.LocalClient{
			DataDir: "/root/data",
		},
		SMS: smsClient,
	}
	canaConnectServer := &canaconnect.Server{
		DB: &util.FileDB[canaconnect.Data]{
			Path: "/root/data/canaconnect.json",
		},
		SMS: smsClient,
	}
	playgroundServer := &playground.Server{}
	rybkaosServer := &rybkaos.Server{}
	servers := map[string]http.Handler{
		"api.rybka.ca":         apiServer,
		"playground.rybka.ca":  playgroundServer.Handler(),
		"canaconnect.rybka.ca": canaConnectServer.Handler(),
		"rybka.ca": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, "https://www.rybka.ca"+r.URL.RequestURI(), http.StatusMovedPermanently)
		}),
		"os.rybka.ca":     rybkaosServer.Handler(),
		"dl.rybka.ca":     dl.NewServer(),
		"rosary.rybka.ca": rosary.NewServer(),
	}
	mux := http.NewServeMux()
	for host, s := range servers {
		mux.Handle(fmt.Sprintf("%s/", host), s)
	}
	mux.HandleFunc("/auth/login", auth.HandleLogin)
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		parts := strings.Split(r.URL.Path, "/")
		if len(parts[1]) > 0 && util.IsDigit(rune(parts[1][0])) {
			auth.Authenticate(w, r, func(userID string) {
				if userID != parts[1] {
					http.Error(w, "forbidden", http.StatusForbidden)
					return
				}
				serve(userID, w, r)
			})
		} else {
			serve("", w, r)
		}
	})
	m := &autocert.Manager{
		Prompt: autocert.AcceptTOS,
		Cache:  autocert.DirCache("/etc/edge/certs"),
		HostPolicy: func(ctx context.Context, host string) error {
			_, ok := servers[host]
			if !ok {
				ok := dataDirExists(host)
				fmt.Println(ok)
				if !ok {
					return fmt.Errorf("bad host: %s", host)
				}
			}
			return nil
		},
	}
	server := &http.Server{
		Addr: ":443",
		TLSConfig: &tls.Config{
			GetCertificate: m.GetCertificate,
		},
		Handler: mux,
	}
	go func() {
		log.Fatal(http.ListenAndServe(":80", m.HTTPHandler(nil)))
	}()
	go func() {
		log.Fatal(server.ListenAndServeTLS("", "")) // Cert and key are managed by autocert
	}()
	select {}
}

func dataDirExists(host string) bool {
	fi, err := os.Stat(filepath.Join("/root/data", host))
	if err != nil {
		return false
	}
	return fi.IsDir()
}

func serve(userID string, w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		http.FileServer(http.Dir(filepath.Join("/root/data", r.Host))).ServeHTTP(w, r)
		return
	}
	if r.Method == "POST" {
		cmd := exec.Command("/usr/bin/go", "run", filepath.Join("/root/src/cmd", r.Host, r.URL.Path, "main.go"), userID)
		cmd.Env = []string{"HOME=/root"}
		cmd.Stdin = r.Body
		out, err := cmd.CombinedOutput()
		if err != nil {
			http.Error(w, string(out), http.StatusBadRequest)
			return
		}
		w.Write(out)
	}
}
