package main

import (
	"context"
	"log"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/mrhid6/vantage/server/internal/api"
	"github.com/mrhid6/vantage/server/internal/auth"
	"github.com/mrhid6/vantage/server/internal/db"
	grpcserver "github.com/mrhid6/vantage/server/internal/grpc"
	"github.com/mrhid6/vantage/server/internal/monitorsched"
	"github.com/mrhid6/vantage/server/internal/services"
)

func main() {
	mongoURI := getEnv("MONGO_URI", "mongodb://localhost:27017")
	dbName := getEnv("MONGO_DB", "vantage")

	if os.Getenv("GRPC_HOST") == "" {
		log.Fatal("GRPC_HOST is required (host:port agents dial for gRPC)")
	}

	if err := db.Connect(mongoURI, dbName); err != nil {
		log.Fatalf("failed to connect to MongoDB: %v", err)
	}
	log.Println("connected to MongoDB")

	// Migrations 0001 to 0003 still speak the pre-rename shape (orgs, org_id),
	// so they must run before 0004 renames everything underneath them.
	if err := services.RunMigrations(); err != nil {
		log.Fatalf("migration failed: %v", err)
	}
	// 0002 must precede 0003: 0003 can create a "default" org, which pushes
	// 0002 into its ambiguous multi-org branch.
	if err := services.MigrateSettingsOrg(); err != nil {
		log.Fatalf("settings org migration failed: %v", err)
	}
	if err := services.MigrateMissedOrgScopes(); err != nil {
		log.Fatalf("missed org scope migration failed: %v", err)
	}

	// 0004 renames orgs to instances. It must run BEFORE the index builders:
	// EnsureAuthIndexes creates instances.slug, which would create an empty
	// instances collection and make 0004 refuse to rename onto it.
	migCtx, migCancel := context.WithTimeout(context.Background(), 10*time.Minute)
	migErr := services.MigrateOrgToInstance(migCtx, db.Database)
	migCancel()
	if migErr != nil {
		log.Fatalf("instance rename migration failed: %v", migErr)
	}

	assertCtx, assertCancel := context.WithTimeout(context.Background(), 30*time.Second)
	assertErr := services.AssertNoScopedCollectionMissed(assertCtx, db.Database)
	assertCancel()
	if assertErr != nil {
		log.Fatalf("scoped collection check failed: %v", assertErr)
	}

	if err := services.EnsureAuthIndexes(); err != nil {
		log.Fatalf("failed to ensure auth indexes: %v", err)
	}

	if err := services.EnsureSecretIndexes(); err != nil {
		log.Printf("warning: failed to ensure secret indexes: %v", err)
	}

	if err := services.EnsureSettingsIndexes(); err != nil {
		log.Fatalf("failed to ensure settings indexes: %v", err)
	}

	if err := services.EnsureWorkflowIndexes(); err != nil {
		log.Printf("warning: failed to ensure workflow indexes: %v", err)
	}

	if instanceIDs, err := services.ListInstanceIDs(); err != nil {
		log.Printf("warning: failed to list instances for default step seeding: %v", err)
	} else {
		for _, instanceID := range instanceIDs {
			if created, updated, err := services.SeedDefaultSteps(instanceID); err != nil {
				log.Printf("warning: failed to seed default steps for instance %s: %v", instanceID, err)
			} else {
				log.Printf("default steps seeded for instance %s: %d created, %d updated", instanceID, created, updated)
			}
		}
	}

	services.StartLogSweeper()

	redisAddr := getEnv("REDIS_ADDR", "localhost:6379")
	if err := auth.InitRedis(redisAddr); err != nil {
		log.Fatalf("failed to connect to Redis: %v", err)
	}
	log.Println("connected to Redis")

	go func() {
		ticker := time.NewTicker(2 * time.Minute)
		defer ticker.Stop()
		for range ticker.C {
			if err := services.MarkOfflineServers(); err != nil {
				log.Printf("mark offline error: %v", err)
			}
		}
	}()

	go func() {
		if err := grpcserver.StartGRPC(9090); err != nil {
			log.Fatalf("gRPC server error: %v", err)
		}
	}()

	monitorsched.Start(context.Background())

	r := gin.New()
	r.Use(gin.Recovery())
	r.Use(gin.LoggerWithConfig(gin.LoggerConfig{SkipPaths: []string{"/api/console/tunnel"}}))
	r.Use(corsMiddleware())
	api.RegisterRoutes(r)

	log.Println("REST server listening on :8080")
	if err := r.Run(":8080"); err != nil {
		log.Fatalf("REST server error: %v", err)
	}
}

func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	}
}

func getEnv(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}
