package main

//go:generate esc -o assets.go assets templates

import (
	"bufio"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"plugin"
	"strconv"
	"strings"
	"syscall"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"github.com/justinas/alice"
	"golang.org/x/crypto/ssh/terminal"
)

const AppName = "ribbit"
const DbName = AppName + ".db"

type menuItem struct {
	Label    string
	Location string
	Icon     string
}

var sessionStore *sessions.CookieStore
var r *mux.Router
var m *model

var scheduler *JobScheduler

func main() {
	var err error
	if m, err = NewModel(); err != nil {
		errorExit("Unable to initialize Model: " + err.Error())
	}

	initialize()
	sessionStore = sessions.NewCookieStore([]byte(m.Site.sessionSecret))

	for _, arg := range os.Args {
		switch arg {
		case "-dev":
			m.Site.DevMode = true
			fmt.Println("Running in Dev Mode")
		}
	}

	r = mux.NewRouter()
	r.StrictSlash(true)
	r.PathPrefix("/assets/").Handler(http.FileServer(FS(m.Site.DevMode)))

	pub := r.PathPrefix("/").Subrouter()
	pub.HandleFunc("/", handleUserRequest)
	/*
		pub.HandleFunc("/api", handleApiCall)
		pub.HandleFunc("/api/users", handleApiUsersCall)
		pub.HandleFunc("/api/users/{uid}", handleApiUsersCall)
		pub.HandleFunc("/api/users/{uid}/{function}", handleApiUsersCall)
		pub.HandleFunc("/api/users/{uid}/{function}/{slug}", handleApiUsersCall)
		pub.HandleFunc("/api/comics", handleApiComicsCall)
		pub.HandleFunc("/api/comics/{cid}", handleApiComicsCall)
		pub.HandleFunc("/api/comics/{cid}/{function}", handleApiComicsCall)
	*/
	pub.HandleFunc("/rss/{uid}", handleRssFeed)
	pub.HandleFunc("/rss/{uid}/{slug}", handleRssFeed)
	pub.HandleFunc("/user/{function}", handleUserRequest)

	http.Handle("/", r)
	chain := alice.New(loggingHandler).Then(r)

	// Refresh the DB at 2 AM
	/*
		go func() {
			for {
				if m.Site.LastSave.IsZero() || (time.Now().Day() != m.Site.LastSave.Day() && time.Now().Hour() == 2) {
					fmt.Println("Updating GoComics List...")
					comics := downloadComicsList()
					for _, c := range comics {
						fmt.Printf("Updating [ %s - %s, %s ]\n", c.Slug, c.Name, c.Artist)
						m.SaveComic(&c)
					}
					fmt.Println("Updating User Feeds...")
					m.UpdateAllUserFeeds()
					m.saveChanges()
					fmt.Println("Done.")
				}
				time.Sleep(time.Minute)
			}
		}()
	*/

	// Set up a channel to intercept Ctrl+C for graceful shutdowns
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		<-c
		// Save the changes when the app quits
		fmt.Println("\nFinishing up...")
		m.saveChanges()
		os.Exit(0)
	}()

	fmt.Printf("Listening on port %d\n", m.Site.Port)
	log.Fatal(http.ListenAndServe("127.0.0.1:"+strconv.Itoa(m.Site.Port), chain))
}

func loggingHandler(h http.Handler) http.Handler {
	return handlers.LoggingHandler(os.Stdout, h)
}

func initPageData(w http.ResponseWriter, req *http.Request) *pageData {
	if m.Site.DevMode {
		w.Header().Set("Cache-Control", "no-cache")
	}
	p := new(pageData)

	// Get session
	var err error
	var s *sessions.Session
	if s, err = sessionStore.Get(req, m.Site.sessionSecret); err != nil {
		fmt.Println("Session error... Recreating.")
		http.Error(w, err.Error(), 500)
	}
	p.session = new(pageSession)
	p.session.session = s
	p.session.req = req
	p.session.w = w

	// First check if we're logged in
	userId, _ := p.session.getStringValue("id")
	// With a valid account
	user, err := m.GetUser(userId)
	if err == nil {
		p.LoggedIn = true
		p.IsAdmin = user.IsAdmin
	}

	p.Site = m.Site
	p.SubTitle = "ribbit"
	p.Stylesheets = make([]string, 0, 0)
	p.Stylesheets = append(p.Stylesheets, "/assets/vendor/css/pure-min.css")
	p.Stylesheets = append(p.Stylesheets, "/assets/vendor/css/grids-responsive-min.css")
	p.Stylesheets = append(p.Stylesheets, "/assets/css/main.css")

	p.HeaderScripts = make([]string, 0, 0)

	p.Scripts = make([]string, 0, 0)

	p.FlashMessage, p.FlashClass = p.session.getFlashMessage()
	if p.FlashClass == "" {
		p.FlashClass = "hidden"
	}

	// Build the menu
	if p.LoggedIn {
		p.BottomMenu = append(p.BottomMenu, menuItem{"Logout", "/user/logout", "fa-sign-out"})
	} else {
		if p.IsAdmin {
			p.BottomMenu = append(p.BottomMenu, menuItem{"Admin", "/admin", "fa-sign-in"})
		}
	}

	return p
}

func outputTemplate(tmplName string, tmplData interface{}, w http.ResponseWriter) error {
	n := "/templates/" + tmplName
	l := template.Must(template.New("layout").Parse(FSMustString(m.Site.DevMode, n)))
	t := template.Must(l.Parse(FSMustString(m.Site.DevMode, n)))
	return t.Execute(w, tmplData)
}

// redirect can be used only for GET redirects
func redirect(url string, w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, url, 303)
}

func done() {
	os.Exit(0)
}

func errorExit(msg string) {
	fmt.Println(msg)
	os.Exit(1)
}

func assertError(err error) {
	if err != nil {
		panic(err)
	}
}

func initialize() {
	// Check if we have an admin user already
	if !m.hasAdminUser() {
		// Nope, create one
		reader := bufio.NewReader(os.Stdin)
		fmt.Println("Create new Admin user")
		fmt.Print("Email: ")
		email, _ := reader.ReadString('\n')
		email = strings.TrimSpace(email)
		u := NewUser(email)
		var pw1, pw2 []byte
		for string(pw1) != string(pw2) || string(pw1) == "" {
			fmt.Print("Password: ")
			pw1, _ = terminal.ReadPassword(0)
			fmt.Println("")
			fmt.Print("Repeat Password: ")
			pw2, _ = terminal.ReadPassword(0)
			fmt.Println("")
			if string(pw1) != string(pw2) {
				fmt.Println("Entered Passwords don't match!")
			}
		}
		u.IsAdmin = true
		m.SaveUser(u)
		assertError(m.updateUserPassword(u.Uuid, string(pw1)))
	}

	if m.Site.sessionSecret == "" {
		reader := bufio.NewReader(os.Stdin)
		fmt.Println("A good session secret is like a good password")
		fmt.Print("Create New Session Secret: ")
		sessSc, _ := reader.ReadString('\n')
		sessSc = strings.TrimSpace(sessSc)
		m.Site.sessionSecret = sessSc
		assertError(m.Site.SaveToDB())
	}

	// Load Feed Sources from Plugins
	files, err := ioutil.ReadDir("./plugins/")
	if err != nil {
		log.Fatal(err)
	}
	for _, f := range files {
		fmt.Print("Loading plugin ", f.Name(), "...")
		p, err := plugin.Open("./plugins/" + f.Name())
		if err != nil {
			panic(err)
		}
		var feedSource *FeedSource
		if feedSource, err = LoadFeedPlugin(p); err != nil {
			fmt.Println("  ", err.Error())
			continue
		}
		m.FeedSources = append(m.FeedSources, *feedSource)
		fmt.Printf("Done. (%d feeds)\n", len(feedSource.Feeds))
	}
}
