package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gorilla/sessions"
	"github.com/joho/godotenv"
	_ "github.com/lib/pq"

	"github.com/forgeo/forgebucket/internal/api"
	"github.com/forgeo/forgebucket/internal/config"
	"github.com/forgeo/forgebucket/internal/db"
	"github.com/forgeo/forgebucket/internal/domain/ci"
	gitdomain "github.com/forgeo/forgebucket/internal/domain/git"
	"github.com/forgeo/forgebucket/internal/domain/gitops"
	"github.com/forgeo/forgebucket/internal/domain/oci"
	"github.com/forgeo/forgebucket/internal/domain/scanning"
	"github.com/forgeo/forgebucket/internal/domain/vulnscan"
	"github.com/forgeo/forgebucket/internal/domain/sbom"
	"github.com/forgeo/forgebucket/internal/domain/signing"
	"github.com/forgeo/forgebucket/internal/events"
	"github.com/forgeo/forgebucket/internal/observability"
	"github.com/forgeo/forgebucket/internal/models/migrations"
	"github.com/forgeo/forgebucket/web"
)

func main() {
	_ = godotenv.Load()

	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("config: %v", err)
	}

	engine, err := db.Open(cfg.DatabaseURL, cfg.Debug)
	if err != nil {
		log.Fatalf("database: %v", err)
	}
	defer engine.Close()

	if err := migrations.Run(engine); err != nil {
		log.Fatalf("migrations: %v", err)
	}

	gitdomain.SetRepoRoot(cfg.RepoRoot)

	if err := os.MkdirAll(cfg.ArtifactRoot, 0755); err != nil {
		log.Fatalf("artifact root: %v", err)
	}

	ociRegistry, err := oci.New(cfg.OCIRoot)
	if err != nil {
		log.Fatalf("oci: %v", err)
	}
	log.Printf("oci: registry initialised at %s", cfg.OCIRoot)

	bus, err := events.New(cfg.NATSUrl)
	if err != nil {
		log.Fatalf("events: %v", err)
	}
	defer bus.Close()

	store := sessions.NewCookieStore([]byte(cfg.SessionSecret))
	store.Options = &sessions.Options{
		Path:     "/",
		MaxAge:   86400 * 7,
		HttpOnly: true,
		Secure:   !cfg.Debug,
		SameSite: http.SameSiteLaxMode,
	}

	// Start CI orchestrator and runner manager in background goroutines.
	ciCtx, ciCancel := context.WithCancel(context.Background())
	defer ciCancel()

	orchestrator := ci.NewOrchestrator(engine, bus)
	go orchestrator.Start(ciCtx)

	runnerMgr := ci.NewRunnerManager(engine, bus, cfg, 4)
	go runnerMgr.Start(ciCtx)

	gitopsCtrl := gitops.NewController(engine, bus, cfg)
	go gitopsCtrl.Start(ciCtx)

	sbomGen := sbom.NewGenerator(engine, bus)
	go sbomGen.Start(ciCtx)

	go observability.StartNATSWatcher(ciCtx, bus)

	// Initialise artifact signing key store.
	var keyStore *signing.KeyStore
	if cfg.ArtifactSigningKey != "" {
		keyStore, err = signing.New(cfg.ArtifactSigningKey)
		if err != nil {
			log.Fatalf("signing: %v", err)
		}
	} else {
		keyStore, err = signing.Generate()
		if err != nil {
			log.Fatalf("signing: %v", err)
		}
	}
	log.Printf("signing: key store initialised (keyId=%s)", keyStore.KeyID())

	secretScanner, err := scanning.New(engine, bus)
	if err != nil {
		log.Fatalf("secret scanner: %v", err)
	}
	go secretScanner.Start(ciCtx)

	vulnScanner := vulnscan.NewScanner(engine, bus)
	go vulnScanner.Start(ciCtx)

	handler := api.New(cfg, engine, store, bus, cfg.ArtifactRoot, web.FS(), *keyStore, sbomGen, ociRegistry, secretScanner, vulnScanner)

	srv := &http.Server{
		Addr:         fmt.Sprintf(":%s", cfg.Port),
		Handler:      handler,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 60 * time.Second,
		IdleTimeout:  120 * time.Second,
	}

	go func() {
		log.Printf("ForgeBucket listening on http://localhost:%s", cfg.Port)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("server: %v", err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("shutdown: %v", err)
	}
	log.Println("ForgeBucket stopped")
}
