// cctv server entrypoint
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"log/slog"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"

	"github.com/go-gst/go-gst/gst"
	"github.com/joho/godotenv"
	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/plugins/migratecmd"

	"git.koval.net/cyclane/cctv/server/config"
	"git.koval.net/cyclane/cctv/server/ingest"
	_ "git.koval.net/cyclane/cctv/server/migrations"
)

var (
	ingestService *ingest.Ingest
)

func loadEnvFile(filenames ...string) {
	for _, filename := range filenames {
		if err := godotenv.Load(filename); err == nil {
			log.Printf("Loaded environment variables from %s", filename)
		}
	}
}

func exportCollectionsJSON(e *core.BootstrapEvent) error {
	if err := e.Next(); err != nil {
		return err
	}

	collections, err := e.App.FindAllCollections()
	if err != nil {
		return fmt.Errorf("failed to export collections: %w", err)
	}

	f, err := os.OpenFile("collections.json", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return fmt.Errorf("failed to open collections.json: %w", err)
	}
	defer func() {
		if err := f.Close(); err != nil {
			log.Printf("Failed to close collections.json: %v", err)
		}
	}()

	encoder := json.NewEncoder(f)
	encoder.SetIndent("", "\t")
	if err := encoder.Encode(collections); err != nil {
		return fmt.Errorf("failed to write collections to JSON: %w", err)
	}
	return nil
}

func main() {
	loadEnvFile(".env.local", ".env", ".env.default")
	config, err := config.Load()
	if err != nil {
		log.Fatalf("Failed to load configuration: %v", err)
	}

	app := pocketbase.NewWithConfig(pocketbase.Config{
		DefaultDev:           config.IsDev(),
		DefaultDataDir:       config.DbDataDir,
		DefaultEncryptionEnv: config.DbEncryptionKey,
	})

	migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
		// enable auto creation of migration files when making collection changes in the Dashboard
		Automigrate: config.IsDev(),
	})

	if config.IsDev() {
		app.OnBootstrap().BindFunc(exportCollectionsJSON)
	}
	var proxy *httputil.ReverseProxy
	if config.ExternalWebApp != "" {
		proxyURL, err := url.Parse(config.ExternalWebApp)
		if err != nil {
			log.Fatalf("Failed to parse external web app URL: %v", err)
		}
		proxy = httputil.NewSingleHostReverseProxy(proxyURL)
		proxy.ErrorLog = slog.NewLogLogger(app.Logger().With("svc", "proxy").Handler(), slog.LevelDebug)
	}
	ingestCtx, cancelIngestCtx := context.WithCancel(context.Background())
	app.OnServe().BindFunc(func(se *core.ServeEvent) error {
		gst.Init(nil)
		ingestService = ingest.BeginIngest(ingestCtx, app)

		if !config.IsDev() {
			se.Router.BindFunc(func(e *core.RequestEvent) error {
				if strings.HasPrefix(e.Request.URL.Path, "/_/") {
					return e.NotFoundError("Page not found", nil)
				} else {
					return e.Next()
				}
			})
		}

		if proxy == nil {
			// TODO: serve bundled (static) files
		} else {
			se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
				proxy.ServeHTTP(e.Response, e.Request)
				return nil
			})
		}

		registerAPI(se)

		return se.Next()
	})

	app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error {
		app.Logger().Info("Shutting down ingest service")
		cancelIngestCtx()
		return nil
	})

	app.OnRecordCreateExecute().BindFunc(handleCameraUpdate)
	app.OnRecordUpdateExecute().BindFunc(handleCameraUpdate)

	if err := app.Start(); err != nil {
		app.Logger().Info("Application terminated with error")
		cancelIngestCtx()
		log.Fatal(err)
	}
	ingestService.Wait()
}
