package main

import (
	"fmt"
	"net/http"
	"path/filepath"
	"hyperia/config"
	"hyperia/db"
	"hyperia/handlers"
	"hyperia/logger"
	// "runtime/debug"
	"strings"

	"github.com/gorilla/websocket"
	"github.com/golang-jwt/jwt/v5"
	"github.com/rs/zerolog/log"
)

func isWebSocketRequest(r *http.Request) bool {
	connHeader := strings.ToLower(r.Header.Get("Connection"))
	upgradeHeader := strings.ToLower(r.Header.Get("Upgrade"))
	return strings.Contains(connHeader, "upgrade") && upgradeHeader == "websocket"
}

func main() {
	config.SetConfiguration()
	logger.ConfigureLogger()

	err := db.InitDB()
	if err != nil {
		log.Fatal().Msgf("failed to connect to database: %v", err)
	} else {
		log.Info().Msg("successfully connected to PostgreSQL")
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Keeps server from crashing if a request fails
		// defer func() {
		// 	if r := recover(); r != nil {
		// 		log.Error().
		// 			Interface("panic_reason", r).
		// 			Bytes("stack_trace", debug.Stack()).
		// 			Msg("panic in http goroutine")
		// 	}
		// }()

		if(loggedIn(w, r)) {
			log.Info().Msg("logged")
			if isWebSocketRequest(r) {
				handleWebSocket(w, r)
				return
			}
			handleSite(w, r)
		} else {
			handlePublic(w, r)
		}
	})

	log.Info().Msgf("Server starting on http://localhost:%s", config.PORT)
	err = http.ListenAndServe(":"+config.PORT, nil)
	if err != nil {
		log.Fatal().Msgf("failed to start server: %v", err)
	}
}

func handlePublic(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/api/signup" {
		handlers.HandleSignup(w, r)
		return
	}
	if r.URL.Path == "/api/login" {
		handlers.HandleLogin(w, r)
		return
	}
	if strings.HasPrefix(r.URL.Path, "/_") {
		handleAsset(w, r)
		return
	}

	servePublicFile(w, r)
}

func servePublicFile(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path

	if path == "/" {

		// Required for sign in / sign out redirect to work properly
		w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
		w.Header().Set("Pragma", "no-cache")
		w.Header().Set("Expires", "0")
		w.Header().Set("Surrogate-Control", "no-store")

		path = "/index.html"
	} else if !strings.Contains(path, ".") {
		path = filepath.Join("/pages", path) + ".html"
	}

	filePath := filepath.Join("../ui/public", path)
	log.Debug().Msgf("serving: %s", filePath)
	http.ServeFile(w, r, filePath)
}

func handleSite(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/signout" {
		handlers.HandleLogout(w, r)
		return
	}
	if strings.HasPrefix(r.URL.Path, "/_") {
		handleAsset(w, r)
		return
	}

	serveSiteFiles(w, r)
}

func serveSiteFiles(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path

	if path == "/" {

		// Required for sign in / sign out redirect to work properly
		w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
		w.Header().Set("Pragma", "no-cache")
		w.Header().Set("Expires", "0")
		w.Header().Set("Surrogate-Control", "no-store")

		path = "/index.html"
	} else if !strings.Contains(path, ".") {
		path = filepath.Join("/pages", path) + ".html"
	}

	filePath := filepath.Join("../ui/site", path)
	log.Debug().Msgf("serving: %s", filePath)
	http.ServeFile(w, r, filePath)
}

func handleAsset(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	filePath := filepath.Join("../ui", path)
	log.Debug().Msgf("serving asset: %s", filePath)
	http.ServeFile(w, r, filePath)
}

func loggedIn(w http.ResponseWriter, r *http.Request) bool {
	cookie, err := r.Cookie("auth_token")
	if err != nil {
		log.Warn().Msg("Unauthorized - missing auth token")
		return false
	}
	jwtToken := cookie.Value

	token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(config.JWT_SECRET), nil
	})

	if err != nil {
		log.Err(err).Msg("error authenticating jwt")
		return false
	}
	if err != nil || !token.Valid {
		return false
	}

	return true
}


var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true // In production, validate the origin!
	},
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		fmt.Println("WebSocket upgrade failed:", err)
		http.Error(w, "WebSocket upgrade failed", http.StatusBadRequest)
		return
	}
	defer conn.Close()

	fmt.Println("WebSocket connection established")

	for {
		msgType, msg, err := conn.ReadMessage()
		if err != nil {
			fmt.Println("Read error:", err)
			break
		}
		fmt.Printf("Received: %s\n", msg)

		if err := conn.WriteMessage(msgType, msg); err != nil {
			fmt.Println("Write error:", err)
			break
		}
	}
}