package webserver

import (
	"crypto/tls"
	// "encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"runtime"
	"strconv"
	"strings"
	"time"

	"gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence"
	"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
)

type Middleware func(http.Handler) http.Handler

type Application struct {
	accessLog *log.Logger
	traceLog  *log.Logger
	InfoLog   *log.Logger
	ErrorLog  *log.Logger

	Middlewares []Middleware

	Profile            persistence.Profile
	ActiveUser         scraper.User
	IsScrapingDisabled bool
}

func NewApp(profile persistence.Profile) Application {
	ret := Application{
		accessLog: log.New(os.Stdout, "ACCESS\t", log.Ldate|log.Ltime),
		traceLog:  log.New(os.Stdout, "TRACE\t", log.Ldate|log.Ltime),
		InfoLog:   log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime),
		ErrorLog:  log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile),

		Profile:            profile,
		ActiveUser:         get_default_user(),
		IsScrapingDisabled: true, // Until an active user is set
	}
	ret.Middlewares = []Middleware{
		secureHeaders,
		ret.logRequest,
		ret.recoverPanic,
	}
	return ret
}

func (app *Application) WithMiddlewares() http.Handler {
	var ret http.Handler = app
	for i := range app.Middlewares {
		ret = app.Middlewares[i](ret)
	}
	return ret
}

func (app *Application) SetActiveUser(handle scraper.UserHandle) error {
	if handle == "no account" {
		app.ActiveUser = get_default_user()
		app.IsScrapingDisabled = true // API requests will fail b/c not logged in
	} else {
		user, err := app.Profile.GetUserByHandle(handle)
		if err != nil {
			return fmt.Errorf("set active user to %q: %w", handle, err)
		}
		scraper.InitApi(app.Profile.LoadSession(handle))
		app.ActiveUser = user
		app.IsScrapingDisabled = false
	}
	return nil
}

func get_default_user() scraper.User {
	return scraper.User{
		ID:                    0,
		Handle:                "[nobody]",
		DisplayName:           "[Not logged in]",
		ProfileImageLocalPath: path.Base(scraper.DEFAULT_PROFILE_IMAGE_URL),
		IsContentDownloaded:   true,
	}
}

// Manual router implementation.
// I don't like the weird matching behavior of http.ServeMux, and it's not hard to write by hand.
func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		http.Redirect(w, r, "/timeline", 303)
		return
	}

	parts := strings.Split(r.URL.Path, "/")[1:]
	switch parts[0] {
	case "static":
		if use_embedded == "true" {
			// Serve directly from the embedded files
			http.FileServer(http.FS(embedded_files)).ServeHTTP(w, r)
		} else {
			http.StripPrefix("/static", http.FileServer(http.Dir(get_filepath("static")))).ServeHTTP(w, r)
		}
	case "tweet":
		app.TweetDetail(w, r)
	case "content":
		http.StripPrefix("/content", http.FileServer(http.Dir(app.Profile.ProfileDir))).ServeHTTP(w, r)
	case "login":
		app.Login(w, r)
	case "change-session":
		app.ChangeSession(w, r)
	case "timeline":
		app.Timeline(w, r)
	case "follow":
		app.UserFollow(w, r)
	case "unfollow":
		app.UserUnfollow(w, r)
	case "search":
		http.StripPrefix("/search", http.HandlerFunc(app.Search)).ServeHTTP(w, r)
	case "lists":
		http.StripPrefix("/lists", http.HandlerFunc(app.Lists)).ServeHTTP(w, r)
	case "bookmarks":
		app.Bookmarks(w, r)
	case "messages":
		http.StripPrefix("/messages", http.HandlerFunc(app.Messages)).ServeHTTP(w, r)
	case "nav-sidebar-poll-updates":
		app.NavSidebarPollUpdates(w, r)
	default:
		app.UserFeed(w, r)
	}
}

func (app *Application) Run(address string, should_auto_open bool) {
	srv := &http.Server{
		Addr:     address,
		ErrorLog: app.ErrorLog,
		Handler:  app.WithMiddlewares(),
		TLSConfig: &tls.Config{
			CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
		},
		IdleTimeout:  time.Minute,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}

	app.InfoLog.Printf("Starting server on %s", address)

	app.start_background()

	if should_auto_open {
		go openWebPage("http://" + address + "/login")
	}
	err := srv.ListenAndServe()
	app.ErrorLog.Fatal(err)
}

func openWebPage(url string) {
	var cmd *exec.Cmd
	switch runtime.GOOS {
	case "darwin": // macOS
		cmd = exec.Command("open", url)
	case "windows":
		cmd = exec.Command("cmd", "/c", "start", url)
	default: // Linux and others
		cmd = exec.Command("xdg-open", url)
	}
	if err := cmd.Run(); err != nil {
		log.Printf("Failed to open homepage: %s", err.Error())
	}
}

func parse_cursor_value(c *persistence.Cursor, r *http.Request) error {
	cursor_param := r.URL.Query().Get("cursor")
	if cursor_param != "" {
		var err error
		c.CursorValue, err = strconv.Atoi(cursor_param)
		if err != nil {
			return fmt.Errorf("attempted to parse cursor value %q as int: %w", c.CursorValue, err)
		}
		c.CursorPosition = persistence.CURSOR_MIDDLE
	}
	return nil
}
