package webserv

import (
	"embed"
	_ "embed"
	"errors"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"sync"

	"imuslab.com/zoraxy/mod/database"
	"imuslab.com/zoraxy/mod/info/logger"
	"imuslab.com/zoraxy/mod/utils"
	"imuslab.com/zoraxy/mod/webserv/filemanager"
)

/*
	Static Web Server package

	This module host a static web server
*/

//go:embed templates/*
var templates embed.FS

/*
WebServerOptions define the default option for the webserv
might get override by user settings loaded from db

Any changes in here might need to also update the StaticWebServerStatus struct
in handler.go. See handler.go for more information.
*/
type WebServerOptions struct {
	Port                        string             //Port for listening
	EnableDirectoryListing      bool               //Enable listing of directory
	WebRoot                     string             //Folder for stroing the static web folders
	EnableWebDirManager         bool               //Enable web file manager to handle files in web directory
	DisableListenToAllInterface bool               // Disable listening to all interfaces, only listen to localhost
	Logger                      *logger.Logger     //System logger
	Sysdb                       *database.Database //Database for storing configs
}

type WebServer struct {
	FileManager *filemanager.FileManager

	mux       *http.ServeMux
	server    *http.Server
	option    *WebServerOptions
	isRunning bool
	mu        sync.Mutex
}

// NewWebServer creates a new WebServer instance. One instance only
func NewWebServer(options *WebServerOptions) *WebServer {
	if options.Logger == nil {
		options.Logger, _ = logger.NewFmtLogger()
	}
	if !utils.FileExists(options.WebRoot) {
		//Web root folder not exists. Create one with default templates
		os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
		os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
		indexTemplate, err := templates.ReadFile("templates/index.html")
		if err != nil {
			options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err)
		} else {
			os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
		}

	}

	//Create a new file manager if it is enabled
	var newDirManager *filemanager.FileManager
	if options.EnableWebDirManager {
		fm := filemanager.NewFileManager(filepath.Join(options.WebRoot, "/html"))
		newDirManager = fm
	}

	//Create new table to store the config
	options.Sysdb.NewTable("webserv")
	return &WebServer{
		mux:         http.NewServeMux(),
		FileManager: newDirManager,
		option:      options,
		isRunning:   false,
		mu:          sync.Mutex{},
	}
}

// Restore the configuration to previous config
func (ws *WebServer) RestorePreviousState() {
	//Set the port
	port := ws.option.Port
	ws.option.Sysdb.Read("webserv", "port", &port)
	ws.option.Port = port

	//Set the enable directory list
	enableDirList := ws.option.EnableDirectoryListing
	ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
	ws.option.EnableDirectoryListing = enableDirList

	//Set disable listen to all interface
	disableListenToAll := ws.option.DisableListenToAllInterface
	ws.option.Sysdb.Read("webserv", "disableListenToAllInterface", &disableListenToAll)
	ws.option.DisableListenToAllInterface = disableListenToAll

	//Check the running state
	webservRunning := true
	ws.option.Sysdb.Read("webserv", "enabled", &webservRunning)
	if webservRunning {
		ws.Start()
	} else {
		ws.Stop()
	}

}

// ChangePort changes the server's port.
func (ws *WebServer) ChangePort(port string) error {
	if IsPortInUse(port) {
		return errors.New("selected port is used by another process")
	}

	if ws.isRunning {
		if err := ws.Stop(); err != nil {
			return err
		}
	}

	ws.option.Port = port
	ws.server.Addr = ":" + port

	err := ws.Start()
	if err != nil {
		return err
	}

	ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil)
	ws.option.Sysdb.Write("webserv", "port", port)

	return nil
}

// Get current using port in options
func (ws *WebServer) GetListeningPort() string {
	return ws.option.Port
}

// Start starts the web server.
func (ws *WebServer) Start() error {
	ws.mu.Lock()
	defer ws.mu.Unlock()

	//Check if server already running
	if ws.isRunning {
		return fmt.Errorf("web server is already running")
	}

	//Check if the port is usable
	if IsPortInUse(ws.option.Port) {
		return errors.New("port already in use or access denied by host OS")
	}

	//Dispose the old mux and create a new one
	ws.mux = http.NewServeMux()

	//Create a static web server
	fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html")))
	ws.mux.Handle("/", ws.fsMiddleware(fs))

	listenAddr := ":" + ws.option.Port
	if ws.option.DisableListenToAllInterface {
		listenAddr = "127.0.0.1:" + ws.option.Port
	}
	ws.server = &http.Server{
		Addr:    listenAddr,
		Handler: ws.mux,
	}

	go func() {
		if err := ws.server.ListenAndServe(); err != nil {
			if err != http.ErrServerClosed {
				ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err)
			}
		}
	}()

	ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil)
	ws.isRunning = true
	ws.option.Sysdb.Write("webserv", "enabled", true)
	return nil
}

// Stop stops the web server.
func (ws *WebServer) Stop() error {
	ws.mu.Lock()
	defer ws.mu.Unlock()

	if !ws.isRunning {
		return fmt.Errorf("web server is not running")
	}

	if err := ws.server.Close(); err != nil {
		return err
	}
	ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil)
	ws.isRunning = false
	ws.option.Sysdb.Write("webserv", "enabled", false)
	return nil
}

func (ws *WebServer) Restart() error {
	if ws.isRunning {
		if err := ws.Stop(); err != nil {
			return err
		}
	}

	if err := ws.Start(); err != nil {
		return err
	}

	ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server restarted. Listening on :"+ws.option.Port, nil)
	return nil
}

func (ws *WebServer) IsRunning() bool {
	ws.mu.Lock()
	defer ws.mu.Unlock()
	return ws.isRunning
}

// UpdateDirectoryListing enables or disables directory listing.
func (ws *WebServer) UpdateDirectoryListing(enable bool) {
	ws.option.EnableDirectoryListing = enable
	ws.option.Sysdb.Write("webserv", "dirlist", enable)
}

// Close stops the web server without returning an error.
func (ws *WebServer) Close() {
	ws.Stop()
}
