package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"sync/atomic"
	"syscall"
	"time"
	"unsafe"

	webview "github.com/jchv/go-webview2"
)

var AppVersion = "dev"

var currentHwnd atomic.Uintptr

func setupFileLogging() {
	logPath := filepath.Join(userDataDir(), "debug.log")
	f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return
	}
	log.SetOutput(f)
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func main() {
	setupFileLogging()
	log.Printf("HPL Toolbox starting (pid=%d)", os.Getpid())

	if focusExistingInstance() {
		log.Printf("Another instance is running; focused it and exiting.")
		return
	}

	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		log.Fatal(err)
	}
	port := listener.Addr().(*net.TCPAddr).Port
	log.Printf("Server listening on 127.0.0.1:%d", port)

	mux := buildMux()
	srv := &http.Server{Handler: mux}
	go func() {
		if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
			log.Printf("Server error: %v", err)
		}
	}()

	if err := writeLockFile(port); err != nil {
		log.Printf("Failed to write lock file: %v", err)
	}
	defer removeLockFile()

	webviewTmp, err := os.MkdirTemp("", "hpltoolbox-wv2-*")
	if err != nil {
		log.Fatalf("Failed to create webview temp dir: %v", err)
	}
	defer os.RemoveAll(webviewTmp)

	w := webview.NewWithOptions(webview.WebViewOptions{
		Debug:     false,
		AutoFocus: true,
		DataPath:  webviewTmp,
		WindowOptions: webview.WindowOptions{
			Title:  "HPL Toolbox " + AppVersion,
			Width:  1100,
			Height: 720,
			IconId: 1,
			Center: true,
		},
	})
	if w == nil {
		log.Fatalln("Failed to create webview.")
	}
	defer w.Destroy()

	// Stash HWND for the /api/focus handler.
	currentHwnd.Store(uintptr(w.Window()))

	w.SetSize(1100, 720, webview.HintNone)
	w.Navigate(fmt.Sprintf("http://127.0.0.1:%d/", port))
	w.Run()

	// Window closed — shut the server down cleanly.
	log.Printf("Window closed; shutting down server.")
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	_ = srv.Shutdown(ctx)
}

func buildMux() *http.ServeMux {
	mux := http.NewServeMux()

	// Pages
	mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" && r.URL.Path != "/home" {
			http.NotFound(w, r)
			return
		}
		HomePage(w, r)
	})
	mux.HandleFunc("GET /plec", PlecPage)
	mux.HandleFunc("GET /applovin", ApplovinPage)
	mux.HandleFunc("GET /base64", Base64Page)
	mux.HandleFunc("GET /mraid", MraidPage)
	mux.HandleFunc("GET /mintegral", MintegralPage)
	mux.HandleFunc("GET /mobile", MobilePage)
	mux.HandleFunc("GET /playworks", PlayworksPage)
	mux.HandleFunc("GET /changelog", ChangelogPage)
	mux.HandleFunc("GET /assets/qrcode.min.js", MobileQrScript)

	// Shared API
	mux.HandleFunc("POST /api/clipboard", ClipboardEndpoint)
	mux.HandleFunc("POST /api/open", OpenEndpoint)
	mux.HandleFunc("POST /api/focus", FocusEndpoint)
	mux.HandleFunc("POST /api/betaTools", BetaToolsEndpoint)
	mux.HandleFunc("POST /api/drop/resolve", DropResolveEndpoint)

	// PLEC
	mux.HandleFunc("POST /api/plec/pick", PlecPick)
	mux.HandleFunc("POST /api/plec/pickFolder", PlecPickFolder)
	mux.HandleFunc("POST /api/plec/upload", PlecUpload)

	// AppLovin
	mux.HandleFunc("POST /api/applovin/pick", ApplovinPick)
	mux.HandleFunc("POST /api/applovin/pickFolder", ApplovinPickFolder)
	mux.HandleFunc("POST /api/applovin/upload", ApplovinUpload)
	mux.HandleFunc("POST /api/applovin/saveQr", ApplovinSaveQR)
	mux.HandleFunc("POST /api/applovin/saveAllQrs", ApplovinSaveAllQRs)

	// Base64
	mux.HandleFunc("POST /api/base64/pickFolder", Base64PickFolder)
	mux.HandleFunc("POST /api/base64/pickFiles", Base64PickFiles)
	mux.HandleFunc("POST /api/base64/scan", Base64Scan)

	// MRAID Checker
	mux.HandleFunc("POST /api/mraid/pickFolder", MraidPickFolder)
	mux.HandleFunc("POST /api/mraid/pickFiles", MraidPickFiles)
	mux.HandleFunc("POST /api/mraid/scan", MraidScan)

	// Mintegral Checker
	mux.HandleFunc("POST /api/mintegral/pick", MintegralPick)
	mux.HandleFunc("POST /api/mintegral/load", MintegralLoad)
	mux.HandleFunc("GET /mintegral/runtime", MintegralRuntimePage)
	mux.HandleFunc("GET /mintegral/ad", MintegralAd)
	mux.HandleFunc("GET /mintegral/monitor.js", MintegralMonitor)
	mux.HandleFunc("GET /mintegral/preview-util.js", MintegralPreviewUtil)

	// Send To Mobile
	mux.HandleFunc("POST /api/mobile/pick", MobilePick)
	mux.HandleFunc("POST /api/mobile/pickFolder", MobilePickFolder)
	mux.HandleFunc("POST /api/mobile/start", MobileStart)
	mux.HandleFunc("POST /api/mobile/stop", MobileStop)

	// Playworks Converter
	mux.HandleFunc("POST /api/playworks/pickSource", PlayworksPickSource)
	mux.HandleFunc("POST /api/playworks/pickOutput", PlayworksPickOutput)
	mux.HandleFunc("POST /api/playworks/convert", PlayworksConvert)

	return mux
}

// FocusEndpoint brings the webview window to the foreground. Used by a second
// instance that wants to focus the running one before exiting.
func FocusEndpoint(w http.ResponseWriter, r *http.Request) {
	hwnd := currentHwnd.Load()
	if hwnd != 0 {
		restoreAndFocus(hwnd)
	}
	w.WriteHeader(http.StatusOK)
}

var (
	user32            = syscall.NewLazyDLL("user32.dll")
	procShowWindow    = user32.NewProc("ShowWindow")
	procSetForeground = user32.NewProc("SetForegroundWindow")
	procIsIconic      = user32.NewProc("IsIconic")
)

const (
	swRestore = 9
	swShow    = 5
)

func restoreAndFocus(hwnd uintptr) {
	// If minimized, restore. Otherwise just ensure it's shown.
	iconic, _, _ := procIsIconic.Call(hwnd)
	if iconic != 0 {
		_, _, _ = procShowWindow.Call(hwnd, swRestore)
	} else {
		_, _, _ = procShowWindow.Call(hwnd, swShow)
	}
	_, _, _ = procSetForeground.Call(hwnd)
}

// Silence "imported and not used" complaints if any.
var _ = unsafe.Sizeof(int(0))
