package frontend

import (
	"context"
	"errors"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"time"

	"github.com/google/uuid"
	b "streifling.com/jason/cpolis/cmd/backend"
)

type Session struct {
	ctx    context.Context
	cancel context.CancelFunc
	cookie *http.Cookie
	User   *b.User
}

func newSession(w http.ResponseWriter, c *b.Config, sessionExpiryChan chan<- string, user *b.User) *Session {
	sessionID := uuid.New().String()
	expires := time.Now().Add(time.Hour * time.Duration(c.CookieExpiryHours))
	ctx, cancel := context.WithDeadline(context.Background(), expires)

	session := &Session{
		ctx:    ctx,
		cancel: cancel,
		cookie: &http.Cookie{
			Name:     "cpolis_session",
			Value:    sessionID,
			Expires:  expires,
			Path:     "/",
			HttpOnly: true,
			Secure:   true,
			SameSite: http.SameSiteStrictMode,
		},
		User: user,
	}

	go func() {
		<-session.ctx.Done()
		sessionExpiryChan <- session.cookie.Value
		session.cookie.Expires = time.Now()
		http.SetCookie(w, session.cookie)
	}()

	return session
}

func StartSessions() (map[string]*Session, chan string) {
	sessions := make(map[string]*Session)
	sessionExpiryChan := make(chan string)

	go func() {
		for sessionID := range sessionExpiryChan {
			delete(sessions, sessionID)
		}
	}()

	return sessions, sessionExpiryChan
}

// SessionIsActive is used for verifying that the user is logged in and returns
// a bool.
func SessionIsActive(r *http.Request, s map[string]*Session) bool {
	cookie, err := r.Cookie("cpolis_session")
	if err != nil {
		return false
	}

	_, ok := s[cookie.Value]
	return ok
}

// ManageSession is used for verifying that the user is logged in and returns
// their session and an error. It also handles cases where the user is not
// logged in.
func ManageSession(w http.ResponseWriter, r *http.Request, c *b.Config, s map[string]*Session) (*Session, error) {
	tmpl, tmplErr := template.ParseFiles(filepath.Join(c.WebDir, "templates", "index.html"), filepath.Join(c.WebDir, "templates", "login.html"))

	cookie, err := r.Cookie("cpolis_session")
	if err != nil {
		if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
			return nil, fmt.Errorf("error executing template: %v", err)
		}

		return nil, errors.New("no cookie set")
	}

	session, ok := s[cookie.Value]
	if !ok {
		cookie.Expires = time.Now()
		http.SetCookie(w, cookie)

		if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
			return nil, fmt.Errorf("error executing template: %v", err)
		}

		return nil, errors.New("session does not exist")
	}

	session.cookie.Expires = time.Now().Add(time.Hour * time.Duration(c.CookieExpiryHours))
	http.SetCookie(w, session.cookie)

	return session, nil
}

func Login(c *b.Config, db *b.DB, s map[string]*Session, sessionExpiryChan chan string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userName := r.PostFormValue("username")
		password := r.PostFormValue("password")

		id := db.GetID(userName)
		if id == 0 {
			http.Error(w, fmt.Sprintf("no such user: %v", userName), http.StatusBadRequest)
			return
		}

		if err := db.CheckPassword(id, password); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		user, err := db.GetUser(c, id)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		session := newSession(w, c, sessionExpiryChan, user)
		s[session.cookie.Value] = session
		http.SetCookie(w, session.cookie)

		tmpl, err := template.ParseFiles(filepath.Join(c.WebDir, "templates", "hub.html"))
		if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}

func Logout(c *b.Config, s map[string]*Session) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tmpl, tmplErr := template.ParseFiles(filepath.Join(c.WebDir, "templates", "login.html"))

		cookie, err := r.Cookie("cpolis_session")
		if err != nil {
			if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
				log.Println(err)
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
		}
		cookie.Expires = time.Now()
		http.SetCookie(w, cookie)

		session, ok := s[cookie.Value]
		if !ok {
			if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
				log.Println(err)
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
		}
		session.cancel()

		if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}
}