package launcher

import (
	"embed"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"sort"
	"strings"

	"github.com/giolekva/pcloud/core/installer"
	"github.com/giolekva/pcloud/core/installer/server"

	"github.com/Masterminds/sprig/v3"
	"github.com/gomarkdown/markdown"
	"github.com/gomarkdown/markdown/html"
)

//go:embed templates/launcher.html
var indexHTML embed.FS

//go:embed static/*
var staticAssets embed.FS

type AppLauncherInfo struct {
	Id         string
	Name       string
	Icon       template.HTML
	Help       []HelpDocumentRendered
	URL        string
	DisplayURL string
}

type HelpDocumentRendered struct {
	Title    string
	Contents template.HTML
	Children []HelpDocumentRendered
}

type AppDirectory interface {
	GetAllApps() ([]AppLauncherInfo, error)
}

type AppManagerDirectory struct {
	AppManager *installer.AppManager
}

func (d *AppManagerDirectory) GetAllApps() ([]AppLauncherInfo, error) {
	all, err := d.AppManager.GetAllInstances()
	if err != nil {
		return nil, err
	}
	ret := []AppLauncherInfo{}
	mdRenderer := html.NewRenderer(html.RendererOptions{
		Flags: html.CommonFlags | html.HrefTargetBlank,
	})
	for _, a := range all {
		if a.URL == "" && len(a.Help) == 0 {
			continue
		}
		ret = append(ret, AppLauncherInfo{
			Id:         a.Id,
			Name:       a.AppId,
			Icon:       template.HTML(a.Icon),
			Help:       toMarkdown(a.Help, mdRenderer),
			URL:        a.URL,
			DisplayURL: shortenURL(a.URL, a.Env.Domain),
		})
	}
	sort.Slice(ret, func(i, j int) bool {
		if ret[i].Name == "app-manager" {
			return true
		}
		if ret[j].Name == "app-manager" {
			return false
		}
		if ret[i].Name == "headscale" {
			return ret[j].Name != "app-manager"
		}
		if ret[j].Name == "headscale" {
			return false
		}
		return ret[i].Name < ret[j].Name
	})
	return ret, nil
}

type LauncherServer struct {
	port         int
	authBaseAddr string
	appDirectory AppDirectory
	homeTmpl     *template.Template
}

func NewLauncherServer(
	port int,
	authBaseAddr string,
	appDirectory AppDirectory,
) (*LauncherServer, error) {
	tmpl, err := indexHTML.ReadFile("templates/launcher.html")
	if err != nil {
		return nil, fmt.Errorf("failed to parse template: %v", err)
	}
	t := template.New("index").Funcs(template.FuncMap(sprig.FuncMap())).Funcs(template.FuncMap{
		"GetUserInitials": getUserInitials,
		"CleanAppName":    cleanAppName,
	})
	t, err = t.Parse(string(tmpl))
	if err != nil {
		return nil, fmt.Errorf("failed to parse template: %v", err)
	}
	return &LauncherServer{
		port,
		authBaseAddr,
		appDirectory,
		t,
	}, nil
}

func getUserInitials(username string) string {
	if username == "" {
		return ""
	}
	return strings.ToUpper(username[:1])
}

func cleanAppName(name string) string {
	cleanName := strings.ToLower(name)
	cleanName = strings.ReplaceAll(cleanName, " ", "-")
	return cleanName
}

func shortenURL(url, domain string) string {
	return url
	// return strings.Replace(url, domain, "..", 1)
}

func getLoggedInUser(r *http.Request) (string, error) {
	if user := r.Header.Get("X-Forwarded-User"); user != "" {
		return user, nil
	} else {
		return "", fmt.Errorf("unauthenticated")
	}
	// return "Username", nil
}

func (s *LauncherServer) Start() {
	http.Handle("/static/", server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
	http.HandleFunc("/", s.homeHandler)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
}

type homeHandlerData struct {
	LoggedInUsername string
	AllAppsInfo      []AppLauncherInfo
	AuthBaseAddr     string
}

func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
	loggedInUsername, err := getLoggedInUser(r)
	if err != nil {
		http.Error(w, "User Not Logged In", http.StatusUnauthorized)
		return
	}
	allAppsInfo, err := s.appDirectory.GetAllApps()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	data := homeHandlerData{
		LoggedInUsername: loggedInUsername,
		AllAppsInfo:      allAppsInfo,
		AuthBaseAddr:     s.authBaseAddr,
	}
	if err := s.homeTmpl.Execute(w, data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func toMarkdown(help []installer.HelpDocument, renderer markdown.Renderer) []HelpDocumentRendered {
	if help == nil {
		return nil
	}
	var ret []HelpDocumentRendered
	for _, h := range help {
		ret = append(ret, HelpDocumentRendered{
			Title:    h.Title,
			Contents: template.HTML(markdown.ToHTML([]byte(h.Contents), nil, renderer)),
			Children: toMarkdown(h.Children, renderer),
		})
	}
	return ret
}
