package main

import (
	"embed"
	"encoding/gob"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"

	_ "embed"

	"git.milar.in/milarin/slices"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
)

var (
	intf = flag.String("i", "", "interface")
	port = flag.Uint("p", 80, "port")
)

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

	//go:embed static/*
	StaticFS embed.FS

	Templates *template.Template

	SessionStore = sessions.NewCookieStore(Must(hex.DecodeString(os.Getenv("SESSION_KEY"))))
)

func main() {
	gob.Register([]Bookmark{})
	gob.Register(&Settings{})

	flag.Parse()

	if tmpl, err := template.New("homepage").ParseFS(TemplateFS, "templates/*"); err == nil {
		Templates = tmpl
	} else {
		panic(err)
	}

	r := mux.NewRouter()
	r.HandleFunc("/", handler)
	r.HandleFunc("/customize", customize)
	r.HandleFunc("/save-changes", saveChanges)
	r.HandleFunc("/search", search)
	r.PathPrefix("/static/").Handler(http.FileServer(http.FS(StaticFS)))

	//http.HandleFunc("/style.css", ProvideFile("static/style.css", "text/css"))
	//http.HandleFunc("/customize.js", ProvideFile("static/customize.js", "application/javascript"))
	//http.HandleFunc("/icons.ttf", ProvideFile("static/icons.ttf", "font/ttf"))
	if err := http.ListenAndServe(fmt.Sprintf("%s:%d", *intf, *port), r); err != nil {
		panic(err)
	}
}

func handler(w http.ResponseWriter, r *http.Request) {
	session, _ := SessionStore.Get(r, "settings")

	data := &TmplData{
		text:      GetText(r),
		Bookmarks: GetValueDefault(session, "bookmarks", DefaultBookmarks()),
		Settings:  GetValueDefault(session, "settings", DefaultSettings()),
	}

	if err := Templates.ExecuteTemplate(w, "index.html", data); err != nil {
		panic(err)
	}
}

func ProvideFile(path, contentType string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		file, err := StaticFS.Open(path)
		if err != nil {
			fmt.Println(err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		defer file.Close()
		w.Header().Add("Content-Type", contentType)
		io.Copy(w, file)
	}
}

func search(w http.ResponseWriter, r *http.Request) {
	session, _ := SessionStore.Get(r, "settings")
	settings := GetValueDefault(session, "settings", DefaultSettings())

	if err := r.ParseForm(); err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	query := r.Form.Get("query")

	if uri, err := ParseURI(query); err == nil {
		// url
		w.Header().Add("Location", uri.String())
		w.WriteHeader(http.StatusMovedPermanently)
	} else {
		// search string
		w.Header().Add("Location", fmt.Sprintf(settings.Search, query))
		w.WriteHeader(http.StatusMovedPermanently)
	}

}

func customize(w http.ResponseWriter, r *http.Request) {
	session, _ := SessionStore.Get(r, "settings")

	text := GetText(r)

	bookmarks := slices.Map(GetValueDefault(session, "bookmarks", DefaultBookmarks()), func(b Bookmark) BookmarkData {
		return BookmarkData{
			Bookmark: &b,
			text:     text,
		}
	})

	data := &CustomizeData{
		text:      text,
		Bookmarks: bookmarks,
		Settings:  GetValueDefault(session, "settings", DefaultSettings()),
	}

	if err := Templates.ExecuteTemplate(w, "customize.html", data); err != nil {
		panic(err)
	}
}

func saveChanges(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	session, _ := SessionStore.Get(r, "settings")

	sessionData := &SessionData{}
	err := json.NewDecoder(r.Body).Decode(&sessionData)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Println(err)
		return
	}

	Reorder(sessionData.Bookmarks)
	session.Values["bookmarks"] = sessionData.Bookmarks
	session.Values["settings"] = sessionData.Settings

	if err := session.Save(r, w); err != nil {
		fmt.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}

func ParseURI(uri string) (*url.URL, error) {
	if !strings.HasPrefix(uri, "http://") && !strings.HasPrefix(uri, "https://") {
		uri = "https://" + uri
	}

	ret, err := url.ParseRequestURI(uri)
	if err != nil {
		return nil, err
	}

	splits := strings.Split(ret.Hostname(), ".")
	if len(splits) <= 1 {
		return nil, errors.New("hostname doesn't have a TLD")
	}

	if _, ok := TLDs[splits[len(splits)-1]]; !ok {
		return nil, errors.New("invalid top level domain")
	}

	return ret, nil
}
