package esr

import (
	"bytes"
	"esr/internal/config"
	"esr/internal/livereload"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
)

type Server struct {
	Config     *config.Config
	EntryPoint string
	LiveReload *LiveReload
	Builder    *Builder
}

func NewDevServer(cfg *config.Config, entryPoint string, builder *Builder) *Server {
	return &Server{
		Config:     cfg,
		EntryPoint: entryPoint,
		LiveReload: NewLiveReload(),
		Builder:    builder,
	}
}

func (s *Server) Start() error {
	s.LiveReload.Start()

	http.Handle("/livereload", s.LiveReload)
	http.Handle("/", s)

	go func() {
		log.Printf("esr :: Serving on http://localhost:%d\n", s.Config.Serve.Port)
		if err := http.ListenAndServe(fmt.Sprintf(":%d", s.Config.Serve.Port), nil); err != nil {
			log.Printf("esr :: Server error: %v\n", err)
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	<-sigCh
	log.Println("esr :: Signal received, gracefully shutting down.")
	return nil
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Cache-Control", "no-store")

	dir := filepath.Dir(s.Config.Serve.Html)
	root := filepath.Join(dir, filepath.Clean(r.URL.Path))

	if stat, err := os.Stat(root); err != nil || stat.IsDir() {
		if content, err := s.BuildIndex(); err == nil {
			w.Header().Set("Content-Type", "text/html")
			w.WriteHeader(http.StatusOK)
			w.Write([]byte(content))
		} else {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
		return
	}

	http.ServeFile(w, r, root)
}

func (s *Server) BuildIndex() (string, error) {
	target, err := filepath.Abs(s.Config.Serve.Html)
	if err != nil {
		return "", err
	}

	tmpl, err := template.New("index.html").Funcs(template.FuncMap{
		"livereload": func() template.HTML { return template.HTML(livereload.JsSnippet) },
		"js": func() template.HTML {
			var scripts strings.Builder
			if s.Builder != nil {
				for _, file := range s.Builder.BuiltJS {
					if s.Config.Format == "esm" {
						fmt.Fprintf(&scripts, "<script src=\"/%s\" type=\"module\"></script>", filepath.Base(file))
					} else {
						fmt.Fprintf(&scripts, "<script src=\"/%s\"></script>", filepath.Base(file))
					}
				}
			}
			return template.HTML(scripts.String())
		},
		"css": func() template.HTML {
			var scripts strings.Builder
			if s.Builder != nil {
				for _, file := range s.Builder.BuiltCSS {
					fmt.Fprintf(&scripts, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/%s\"></script>", filepath.Base(file))
				}
			}
			return template.HTML(scripts.String())
		},
	}).ParseFiles(target)

	if err != nil {
		return "", err
	}

	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, nil); err != nil {
		return "", err
	}

	return buf.String(), nil
}

func (s *Server) TriggerRebuild() error {
	opts := ModeOptions(s.Config, ModeServer, s.Builder.EntryPoint, "")
	return s.Builder.Build(opts)
}
