package main

import (
	"crypto/tls"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"

	"golang.org/x/crypto/acme/autocert"
)

var version = "1.2.0"

// Routes: hostname → localhost:port.
// Compiled in — these change maybe once a quarter.
var routes = map[string]string{
	"git.arikigame.com":      "http://127.0.0.1:4100", // Gitea
	"bot.arikigame.com":      "http://127.0.0.1:5500", // bot platform (legacy — use bot.tinqs.com)
	"bot.tinqs.com":          "http://127.0.0.1:5500", // bot platform (new domain)
	"taco.arikigame.com":     "http://127.0.0.1:5500", // meeting assistant (same process as bot)
	"admin.arikigame.com":    "http://127.0.0.1:7700", // admin portal
	"langfuse.arikigame.com": "http://127.0.0.1:3100", // Langfuse LLM observability
	"staging.tinqs.com":     "http://127.0.0.1:3115", // Tinqs Platform (staging) — pm2 platform-staging
	"platform.tinqs.com":    "http://127.0.0.1:3120", // Tinqs Platform (production) — pm2 platform
}

// Tailscale-only hosts — unreachable from public internet. Defense-in-depth.
var tailscaleOnly = map[string]bool{
	"admin.arikigame.com":    true,
	"langfuse.arikigame.com": true,
}

// tailscaleCIDR is 100.64.0.0/10 (CGNAT range Tailscale uses).
var tailscaleCIDR *net.IPNet

func init() {
	_, tailscaleCIDR, _ = net.ParseCIDR("100.64.0.0/10")
}

func isTailscaleIP(remoteAddr string) bool {
	host, _, err := net.SplitHostPort(remoteAddr)
	if err != nil {
		host = remoteAddr
	}
	ip := net.ParseIP(host)
	return ip != nil && tailscaleCIDR.Contains(ip)
}

func realIP(r *http.Request) string {
	host, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		return r.RemoteAddr
	}
	return host
}

func main() {
	proxies := make(map[string]*httputil.ReverseProxy, len(routes))
	domains := make([]string, 0, len(routes))

	for host, target := range routes {
		u, err := url.Parse(target)
		if err != nil {
			log.Fatalf("bad target for %s: %v", host, err)
		}
		rp := &httputil.ReverseProxy{
			Director: func(h string, u *url.URL) func(req *http.Request) {
				return func(req *http.Request) {
					req.URL.Scheme = u.Scheme
					req.URL.Host = u.Host
					req.Host = h
					ip := realIP(req)
					req.Header.Set("X-Real-IP", ip)
					req.Header.Set("X-Forwarded-For", ip)
					req.Header.Set("X-Forwarded-Proto", "https")
				}
			}(host, u),
			ErrorLog: log.New(os.Stderr, "["+host+"] ", log.LstdFlags),
			// Flush immediately for SSE / streaming responses
			FlushInterval: -1,
		}
		proxies[host] = rp
		domains = append(domains, host)
	}

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		host := strings.ToLower(strings.Split(r.Host, ":")[0])

		proxy, ok := proxies[host]
		if !ok {
			http.Error(w, "unknown host", http.StatusNotFound)
			return
		}

		if tailscaleOnly[host] && !isTailscaleIP(r.RemoteAddr) {
			http.Error(w, "Tailscale required — install at https://tailscale.com/download and sign in with @tinqs.com", http.StatusForbidden)
			return
		}

		proxy.ServeHTTP(w, r)
	})

	// Certificate storage
	certDir := "/var/lib/tinqs-proxy/certs"
	if d := os.Getenv("CERT_DIR"); d != "" {
		certDir = d
	}

	mgr := &autocert.Manager{
		Cache:      autocert.DirCache(certDir),
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist(domains...),
	}

	// :80 — ACME HTTP-01 challenges, Tailscale-only hosts served directly, rest redirect to HTTPS
	go func() {
		httpSrv := &http.Server{
			Addr:         ":80",
			ReadTimeout:  5 * time.Second,
			WriteTimeout: 5 * time.Second,
			Handler: mgr.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				host := strings.Split(r.Host, ":")[0]
				// Tailscale-only hosts: serve on HTTP directly (WireGuard is already encrypted)
				if tailscaleOnly[host] {
					proxy, ok := proxies[host]
					if ok && isTailscaleIP(r.RemoteAddr) {
						proxy.ServeHTTP(w, r)
						return
					}
					http.Error(w, "Tailscale required", http.StatusForbidden)
					return
				}
				target := "https://" + r.Host + r.URL.RequestURI()
				http.Redirect(w, r, target, http.StatusMovedPermanently)
			})),
		}
		log.Printf("tinqs-proxy :80 (ACME + redirect)")
		if err := httpSrv.ListenAndServe(); err != nil {
			log.Fatalf(":80 failed: %v", err)
		}
	}()

	// :443 — TLS with autocert
	srv := &http.Server{
		Addr:    ":443",
		Handler: handler,
		TLSConfig: &tls.Config{
			GetCertificate: mgr.GetCertificate,
			MinVersion:     tls.VersionTLS12,
		},
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Minute, // 37 GB repos need time
		IdleTimeout:  5 * time.Minute,
	}

	log.Printf("tinqs-proxy v%s starting on :443", version)
	for _, d := range domains {
		log.Printf("  %s → %s", d, routes[d])
	}
	log.Fatal(srv.ListenAndServeTLS("", ""))
}
