package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"
)

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}

func run() error {
	root, err := repositoryRoot()
	if err != nil {
		return err
	}

	log.Print("building core and handles packages")
	packages, err := buildPackages(root)
	if err != nil {
		return fmt.Errorf("build packages: %w", err)
	}
	log.Printf("loaded %d package assets into memory", len(packages))

	databasePath := os.Getenv("YAWN_DATABASE")
	if databasePath == "" {
		databasePath = filepath.Join(root, "server", "data", "yawn.db")
	}
	store, err := openPlaygroundStore(databasePath)
	if err != nil {
		return fmt.Errorf("open playground database: %w", err)
	}
	defer store.Close()

	app, err := newApplication(
		store,
		packages,
		filepath.Join(root, "node_modules", "monaco-editor", "min"),
	)
	if err != nil {
		return err
	}

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	server := &http.Server{
		Addr:              ":" + port,
		Handler:           app.routes(),
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       15 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       60 * time.Second,
	}

	stopped := make(chan os.Signal, 1)
	signal.Notify(stopped, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-stopped
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		if err := server.Shutdown(ctx); err != nil {
			log.Printf("shutdown: %v", err)
		}
	}()

	log.Printf("Yawn server listening on http://0.0.0.0:%s", port)
	err = server.ListenAndServe()
	if errors.Is(err, http.ErrServerClosed) {
		return nil
	}
	return err
}

func repositoryRoot() (string, error) {
	directory, err := os.Getwd()
	if err != nil {
		return "", err
	}
	for {
		if fileExists(filepath.Join(directory, "Cargo.toml")) &&
			fileExists(filepath.Join(directory, "core", "Cargo.toml")) &&
			fileExists(filepath.Join(directory, "addons", "handles", "package.json")) {
			return directory, nil
		}
		parent := filepath.Dir(directory)
		if parent == directory {
			return "", errors.New("could not find the Yawn repository root")
		}
		directory = parent
	}
}

func fileExists(path string) bool {
	info, err := os.Stat(path)
	return err == nil && !info.IsDir()
}
