package server

import (
	"bytes"
	"esperframework/options"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

func Build(opts *options.Options) {
	// For testing
	if _, exists := os.Stat((*opts).CacheClientPath); os.IsNotExist(exists) {
		os.MkdirAll((*opts).CacheClientPath, os.ModePerm)
	}

	destPath := filepath.Join((*opts).OutPath, "server.go")

	err := ioutil.WriteFile(destPath, []byte(getServer()), os.ModePerm)
	if err != nil {
		log.Fatalf("\nFailed to write server file at %s\n%s\n", destPath, err)
	}

	// For running server within main go process
	// initServer(opts, "server", opts.OutPath, opts.OutPath)
}

type FileType int

const (
	HTML = iota
	GO
)

type RouteData struct {
	fileType   FileType
	route      string
	path       string
	importPath string
	funcName   string
}

var pageRoutes = []RouteData{}

func BuildRoute(opts *options.Options, osPath, name, metaDataPath string, fileType FileType) {
	rnb, _, _ := strings.Cut(metaDataPath, filepath.Base(metaDataPath))
	_, route, _ := strings.Cut(rnb, filepath.Base(opts.AppDir))
	path := filepath.Join((*opts).CacheClientPath, route)

	rd := RouteData{
		fileType: fileType,
		path:     path,
		route:    filepath.Join(route),
	}

	if filepath.Base(route)[0] == '_' && filepath.Base(route)[len(filepath.Base(route))-1] == '_' {
		rd.route = strings.Split(route, filepath.Base(route))[0] + ":" + strings.Trim(filepath.Base(route), "_")
	}

	if filepath.Ext(name) != ".go" {
		pageRoutes = append(pageRoutes, rd)
		return
	}

	route = filepath.Join(strings.Split(rd.route, "index")[0])
	rd.route = filepath.Join(route, "__data")

	if filepath.Base(route) == "/" {
		rd.funcName = "Get"
	} else {
		cleanName := strings.Replace(strings.Replace(opts.Name, "-", "", -1), "_", "", -1)
		cleanRoute := strings.Replace(strings.Replace(strings.Replace(route, "-", "", -1), "_", "", -1), ":", "", -1)
		rd.importPath = filepath.Join(cleanName, cleanRoute)
		rd.funcName = fmt.Sprintf("%s.Get", filepath.Base(cleanRoute))
	}

	pageRoutes = append(pageRoutes, rd)

	// Copy to server dir
	_, rel, _ := strings.Cut(osPath, opts.AppPath)
	rel = strings.Replace(strings.Replace(rel, "_", "", 1), "_", "", 1)
	preDestPath := filepath.Join((*opts).OutPath, rel)
	destPath := strings.Replace(strings.Replace(preDestPath, "-", "", -1), "-", "", -1)
	preDestDir := filepath.Join(strings.Split(destPath, filepath.Base(destPath))[0])
	destDir := strings.Replace(strings.Replace(preDestDir, "-", "", -1), "_", "", -1)

	if filepath.Base(destPath) != "index.go" {
		destPath = filepath.Join(strings.Split(destPath, filepath.Ext(destPath))[0], "index"+filepath.Ext(destPath))
		destDir = filepath.Join(strings.Split(destPath, filepath.Base(destPath))[0])
	}

	if _, err := os.Stat(destDir); os.IsNotExist(err) {
		err := os.MkdirAll(destDir, os.ModePerm)
		if err != nil {
			log.Fatalf("\nFailed to make cache server directory at %s\n%s\n", destDir, err)
		}
	}

	src, err := os.Open(osPath)
	if err != nil {
		log.Fatalf("\nFailed to open file for server at %s\n%s\n", osPath, err)
	}

	dest, err := os.Create(destPath)
	if err != nil {
		log.Fatalf("\nFailed to create file for server at %s\n%s\n", filepath.Join(opts.OutPath, name), err)
	}

	_, err = io.Copy(dest, src)

	src.Close()
	dest.Close()
}

func getServer() string {
	sort.Slice(pageRoutes, func(i, j int) bool {
		return pageRoutes[i].route < pageRoutes[j].route
	})

	sr := ""
	imp := ""

	for _, rm := range pageRoutes {
		if rm.fileType == HTML {
			sr += fmt.Sprintf(`
			app.Get("%s", func(ctx *fiber.Ctx) error {
				ctx.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
				return ctx.SendString(string(readFile("%s", "index.html")))
			})

			app.Post("%s", func(ctx *fiber.Ctx) error {
				ctx.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
				return ctx.SendString(string(readFile("%s", "index.html")))
			})
					`, rm.route, rm.path, rm.route, rm.path)

			continue
		}

		if len(rm.importPath) > 0 {
			imp += "\"" + rm.importPath + "\"\n\t"
		}

		// 	sr += fmt.Sprintf(`
		// app.Get("%s", %s)
		// 	`, rm.route, rm.funcName)
	}

	wd, err := os.Getwd()
	if err != nil {
		log.Fatal("Failed to get working directory")
	}

	path := filepath.Join(wd, "../", "src", "server", "server.tmpl.go")

	file, err := ioutil.ReadFile(path)
	if err != nil {
		log.Fatalf("\nFailed to read server template at %s\n%s\n", path, err)
	}

	var tmpl bytes.Buffer

	for i := 0; i < len(file); i++ {
		// len([]byte("// __EMBEDS__")) == 13
		if file[i] == '/' && bytes.Compare(file[i:i+13], []byte("// __EMBEDS__")) == 0 {
			i += len([]byte("// __EMBEDS__"))
			tmpl.Write([]byte(fmt.Sprintf("%s\n%s\n\n", "//go:embed client/**/*", "var f embed.FS")))
		}

		// len([]byte("// __ROUTES__")) == 13
		if file[i] == '/' && bytes.Compare(file[i:i+13], []byte("// __ROUTES__")) == 0 {
			i += len([]byte("// __ROUTES__"))
			tmpl.Write([]byte(sr))
		}

		// len([]byte("// __IMPORTS__")) == 14
		if file[i] == '/' && bytes.Compare(file[i:i+14], []byte("// __IMPORTS__")) == 0 {
			i += 14
			tmpl.Write([]byte("\"embed\""))
			// TODO: Figure out imports
			// tmpl.Write([]byte(imp))
		}

		tmpl.WriteByte(file[i])
	}

	return strings.Replace(tmpl.String(), "package server", "package main", -1)
}
