package cmd

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"

	"github.com/comp500/packwiz/core"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var refreshMutex sync.RWMutex

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:     "serve",
	Short:   "Run a local development server",
	Long:    `Run a local HTTP server for development, automatically refreshing the index when it is queried`,
	Aliases: []string{"server"},
	Args:    cobra.NoArgs,
	Run: func(cmd *cobra.Command, args []string) {
		if viper.GetBool("serve.basic") {
			http.Handle("/", http.FileServer(http.Dir(".")))
		} else {
			fmt.Println("Loading modpack...")
			pack, err := core.LoadPack()
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			index, err := pack.LoadIndex()
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			indexPath := filepath.Join(filepath.Dir(viper.GetString("pack-file")), filepath.FromSlash(pack.Index.File))

			http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
				path := strings.TrimPrefix(req.URL.Path, "/")

				found := false
				if path == pack.Index.File {
					found = true
					path = indexPath
					// Must be done here, to ensure all paths gain the lock at some point
					refreshMutex.RLock()
				} else if path == filepath.ToSlash(viper.GetString("pack-file")) {
					found = true
					if viper.GetBool("serve.refresh") {
						// Get write lock, to do a refresh
						refreshMutex.Lock()
						// Reload pack and index (might have changed on disk)
						pack, err = core.LoadPack()
						if err != nil {
							fmt.Println(err)
							return
						}
						index, err = pack.LoadIndex()
						if err != nil {
							fmt.Println(err)
							return
						}
						err = index.Refresh()
						if err != nil {
							fmt.Println(err)
							return
						}
						err = index.Write()
						if err != nil {
							fmt.Println(err)
							return
						}
						err = pack.UpdateIndexHash()
						if err != nil {
							fmt.Println(err)
							return
						}
						err = pack.Write()
						if err != nil {
							fmt.Println(err)
							return
						}
						fmt.Println("Index refreshed!")

						// Downgrade to a read lock
						refreshMutex.Unlock()
					}
					refreshMutex.RLock()
				} else {
					refreshMutex.RLock()
					// Only allow indexed files
					for _, v := range index.Files {
						if path == v.File {
							found = true
							break
						}
					}
					if found {
						path = filepath.Join(filepath.Dir(indexPath), filepath.FromSlash(path))
					}
				}
				defer refreshMutex.RUnlock()
				if found {
					f, err := os.Open(path)
					if err != nil {
						fmt.Printf("Error reading file \"%s\": %s\n", path, err)
						w.WriteHeader(404)
						w.Write([]byte("File not found"))
						return
					}
					defer f.Close()
					_, err = io.Copy(w, f)
					if err != nil {
						fmt.Printf("Error reading file \"%s\": %s\n", path, err)
						w.WriteHeader(500)
						w.Write([]byte("Failed to read file"))
						return
					}
				} else {
					fmt.Printf("File not found: %s\n", path)
					w.WriteHeader(404)
					w.Write([]byte("File not found"))
					return
				}
			})
		}

		port := strconv.Itoa(viper.GetInt("serve.port"))
		fmt.Println("Running on port " + port)
		err := http.ListenAndServe(":"+port, nil)
		if err != nil {
			fmt.Printf("Error running server: %s\n", err)
			os.Exit(1)
		}
	},
}

func init() {
	rootCmd.AddCommand(serveCmd)

	serveCmd.Flags().IntP("port", "p", 8080, "The port to run the server on")
	viper.BindPFlag("serve.port", serveCmd.Flags().Lookup("port"))
	serveCmd.Flags().BoolP("refresh", "r", true, "Automatically refresh the index file")
	viper.BindPFlag("serve.refresh", serveCmd.Flags().Lookup("refresh"))
	serveCmd.Flags().Bool("basic", false, "Disable refreshing and allow all files in the directory, rather than just files listed in the index")
	viper.BindPFlag("serve.basic", serveCmd.Flags().Lookup("basic"))
}