package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"slices"
	"strings"
	"time"

	"github.com/emersion/go-webdav/caldav"
	"github.com/emersion/go-webdav/carddav"

	"nxcaldav/internal/backend"
	"nxcaldav/internal/config"
	"nxcaldav/internal/extra"
)

func main() {
	// -- GET CONFIG
	path := "config.yaml"
	if len(os.Args) == 3 {
		if os.Args[1] == "-c" {
			path = os.Args[2]
		}
	}
	cfg, err := config.Load(path)
	if err != nil {
		log.Fatalf("failed to load config: %v", err)
	}

	// -- GET CONTEXT AND DB
	ctx := context.Background()
	be, err := backend.NewDBBackend(ctx, cfg)
	if err != nil {
		log.Fatalf("failed to initialize database backend: %v", err)
	}

	// -- GET CONTEXT AND DB
	caldavHandler := &caldav.Handler{Backend: be}
	carddavHandler := &carddav.Handler{Backend: be}
	publicURL, _ := url.Parse(cfg.Server.PublicURL)

	// -- DISCOVERIES
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

		scheme := r.URL.Scheme
		if scheme == "" {
			scheme = "http"
		}

		// set host (because reverse proxies exist)
		if publicURL != nil && publicURL.Host != "" {
			r.Host = publicURL.Host
			r.URL.Host = publicURL.Host

			// prioritize X-Forwarded-Proto, then PublicURL (e.g. Cloudfalre proxy)
			scheme = publicURL.Scheme
			if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
				scheme = proto
			}
			r.URL.Scheme = scheme

			// Also rewrite WebDAV Destination header (for MOVE/COPY)
			if dest := r.Header.Get("Destination"); dest != "" {
				destURL, err := url.Parse(dest)
				if err == nil {
					destURL.Host = publicURL.Host
					destURL.Scheme = scheme
					r.Header.Set("Destination", destURL.String())
				}
			}
		}

		// public ics access
		prefix := cfg.Server.BasePath()
		if strings.HasPrefix(r.URL.Path, prefix+"/public/") {
			be.ServePublicICS(w, r)
			return
		}

		// DAV needs auth
		user, password, ok := r.BasicAuth()
		if !ok {
			w.Header().Set("WWW-Authenticate", `Basic realm="CalDAV Server"`)
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		valid, err := be.VerifyUser(r.Context(), user, password)
		if err != nil {
			log.Printf("auth error for %s: %v", user, err)
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}
		if !valid {
			log.Printf("auth failed for %s", user)
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		log.Printf("[user: %s] %s %s", user, r.Method, r.URL.Path)

		principalPath := prefix + fmt.Sprintf("/%s/", user)
		ctx := context.WithValue(r.Context(), "principal", principalPath)

		// set header for carddav
		if slices.Contains([]string{
			"/.well-known/carddav",
			prefix + "/.well-known/carddav",
			principalPath,
			strings.TrimSuffix(principalPath, "/"),
		}, r.URL.Path) || strings.Contains(r.URL.Path, "/addressbooks/") {
			w.Header().Add("DAV", "addressbook")
		}

		// for caldav discovery
		if slices.Contains([]string{
			"/.well-known/caldav",
			prefix + "/.well-known/caldav",
		}, r.URL.Path) {
			http.Redirect(w, r, fmt.Sprintf("%s://%s%s", scheme, r.Host, principalPath), http.StatusMovedPermanently)
			return
		}

		// serve caldav
		if strings.Contains(r.URL.Path, "/calendars/") {
			if r.Method == "PROPFIND" {
				// Calendar colors
				// needed because color info is not RFC, so I hacked it in with regex, to look like Radicales response
				extra.InjectColor(r, ctx, caldavHandler, w, be)
			} else {
				caldavHandler.ServeHTTP(w, r.WithContext(ctx))
			}

			// serve carddav
		} else if strings.Contains(r.URL.Path, "/addressbooks/") {
			carddavHandler.ServeHTTP(w, r.WithContext(ctx))

			// catch weird requests
		} else {

			if strings.HasSuffix(r.URL.Path, user+"/") || strings.HasSuffix(r.URL.Path, user) {
				// For principal path, use merged discovery handler
				if r.Method == "PROPFIND" {
					extra.HandleDiscoveryPropfind(r, ctx, caldavHandler, w, be)
				} else {
					caldavHandler.ServeHTTP(w, r.WithContext(ctx))
					// Ensure DAV header includes addressbook for OPTIONS
					if r.Method == "OPTIONS" {
						dav := w.Header().Get("DAV")
						if dav != "" && !strings.Contains(dav, "addressbook") {
							w.Header().Set("DAV", dav+", addressbook")
						}
					}
				}
			} else {
				http.NotFound(w, r)
			}
		}
	})

	fmt.Printf("Starting CalDAV/CardDAV server on %s...\n", cfg.Server.BindAddress)
	server := &http.Server{
		Addr:         cfg.Server.BindAddress,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
	}
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("server failed: %v", err)
	}
}
