package api

import (
	"embed"
	"io"
	"io/fs"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"sync"

	"github.com/rcourtman/pulse-go-rewrite/internal/utils"
	"github.com/rs/zerolog/log"
)

// Embed the entire frontend dist directory
//
//go:embed all:frontend-modern/dist
var embeddedFrontend embed.FS

var (
	devProxyOnce sync.Once
	devProxy     *httputil.ReverseProxy
	devProxyErr  error
)

func getFrontendDevProxy() (*httputil.ReverseProxy, error) {
	devProxyOnce.Do(func() {
		devURL := utils.GetenvTrim("FRONTEND_DEV_SERVER")
		if devURL == "" {
			return
		}

		target, err := url.Parse(devURL)
		if err != nil {
			devProxyErr = err
			return
		}

		proxy := httputil.NewSingleHostReverseProxy(target)
		proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
			log.Error().Err(err).Str("path", r.URL.Path).Msg("Frontend dev proxy error")
			w.WriteHeader(http.StatusBadGateway)
		}
		devProxy = proxy
		log.Warn().Str("frontend_dev_server", target.String()).Msg("Serving frontend via development proxy")
	})

	if devProxyErr != nil {
		return nil, devProxyErr
	}
	return devProxy, nil
}

// getFrontendFS returns the embedded frontend filesystem
func getFrontendFS() (http.FileSystem, error) {
	if dir := utils.GetenvTrim("PULSE_FRONTEND_DIR"); dir != "" {
		log.Warn().Str("frontend_dir", dir).Msg("Serving frontend from filesystem override")
		return http.Dir(dir), nil
	}

	// Strip the prefix to serve files from root
	fsys, err := fs.Sub(embeddedFrontend, "frontend-modern/dist")
	if err != nil {
		return nil, err
	}
	return http.FS(fsys), nil
}

// serveFrontendHandler returns a handler for serving the embedded frontend
func serveFrontendHandler() http.HandlerFunc {
	if proxy, err := getFrontendDevProxy(); err != nil {
		log.Error().Err(err).Msg("Failed to initialize frontend dev proxy, falling back to embedded assets")
	} else if proxy != nil {
		return func(w http.ResponseWriter, r *http.Request) {
			proxy.ServeHTTP(w, r)
		}
	}

	// Get the embedded filesystem
	fsys, err := getFrontendFS()
	if err != nil {
		log.Fatal().Err(err).Msg("Failed to get embedded frontend")
	}

	return func(w http.ResponseWriter, r *http.Request) {
		// Clean the path
		p := r.URL.Path

		// Handle root path specially to avoid FileServer's directory redirect
		// Issue #334: Serve index.html directly without using FileServer for root
		if p == "/" || p == "" {
			// Directly serve index.html content
			file, err := fsys.Open("index.html")
			if err != nil {
				http.NotFound(w, r)
				return
			}
			defer file.Close()

			// Check that it's not a directory
			_, err = file.Stat()
			if err != nil {
				http.NotFound(w, r)
				return
			}

			// Read the file content
			content, err := io.ReadAll(file)
			if err != nil {
				http.NotFound(w, r)
				return
			}

			// Serve the content with cache-busting headers
			w.Header().Set("Content-Type", "text/html; charset=utf-8")
			w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
			w.Header().Set("Pragma", "no-cache")
			w.Header().Set("Expires", "0")
			w.Write(content)
			return
		}

		// Remove leading slash for filesystem lookup
		lookupPath := strings.TrimPrefix(p, "/")

		// Check if file exists in embedded FS
		file, err := fsys.Open(lookupPath)
		if err == nil {
			defer file.Close()

			// Get file info
			stat, err := file.Stat()
			if err == nil && !stat.IsDir() {
				// Read and serve the file
				content, err := io.ReadAll(file)
				if err == nil {
					// Detect content type
					contentType := "application/octet-stream"
					isImmutable := false

					if strings.HasSuffix(lookupPath, ".html") {
						contentType = "text/html; charset=utf-8"
					} else if strings.HasSuffix(lookupPath, ".css") {
						contentType = "text/css; charset=utf-8"
						// CSS files with hashes are immutable (e.g., index-abc123.css)
						isImmutable = strings.Contains(lookupPath, "-") && strings.Contains(lookupPath, ".css")
					} else if strings.HasSuffix(lookupPath, ".js") {
						contentType = "application/javascript; charset=utf-8"
						// JS files with hashes are immutable (e.g., index-BXHytNQV.js)
						isImmutable = strings.Contains(lookupPath, "-") && strings.Contains(lookupPath, ".js")
					} else if strings.HasSuffix(lookupPath, ".json") {
						contentType = "application/json"
					} else if strings.HasSuffix(lookupPath, ".svg") {
						contentType = "image/svg+xml"
					}

					w.Header().Set("Content-Type", contentType)

					// Hashed assets are immutable - cache aggressively
					// Non-hashed assets should not be cached
					if isImmutable {
						w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
					} else {
						w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
						w.Header().Set("Pragma", "no-cache")
						w.Header().Set("Expires", "0")
					}

					w.Write(content)
					return
				}
			}
		}

		// For SPA routing, serve index.html for non-API routes
		if !strings.HasPrefix(p, "/api/") &&
			!strings.HasPrefix(p, "/ws") &&
			!strings.HasPrefix(p, "/socket.io/") {
			// Serve index.html for client-side routing
			indexFile, err := fsys.Open("index.html")
			if err == nil {
				defer indexFile.Close()
				content, err := io.ReadAll(indexFile)
				if err == nil {
					w.Header().Set("Content-Type", "text/html; charset=utf-8")
					w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
					w.Header().Set("Pragma", "no-cache")
					w.Header().Set("Expires", "0")
					w.Write(content)
					return
				}
			}
		}

		// Not found
		http.NotFound(w, r)
	}
}
