package main

import (
	"context"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/application/services"
	"github.com/nexuscrm/backend/internal/bootstrap"
	"github.com/nexuscrm/backend/internal/controlplane"
	"github.com/nexuscrm/backend/internal/controlplane/packs"
	"github.com/nexuscrm/backend/internal/controlplane/tenantoverlay"
	"github.com/nexuscrm/backend/internal/features/audit"
	"github.com/nexuscrm/backend/internal/features/auth"
	"github.com/nexuscrm/backend/internal/infrastructure/database"
	"github.com/nexuscrm/backend/internal/interfaces/middleware"
	"github.com/nexuscrm/backend/internal/interfaces/rest"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"

	_ "github.com/nexuscrm/backend/docs"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
)

// @title           NexusCRM API
// @version         3.0.0
// @description     Enterprise CRM API with Salesforce Compatibility.
// @termsOfService  http://swagger.io/terms/

// @contact.name   API Support
// @contact.url    http://www.swagger.io/support
// @contact.email  support@nexuscrm.com

// @license.name  Apache 2.0
// @license.url   http://www.apache.org/licenses/LICENSE-2.0.html

// @host      localhost:3001
// @BasePath  /api

// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
func main() {
	// Initialize Zerolog
	// Default to JSON (Production Standard)
	// Use pretty printing for local dev if requested
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
	if os.Getenv("LOG_FORMAT") != "json" {
		log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
	}

	// Get port from environment
	port := os.Getenv("PORT")
	if port == "" {
		port = "3001" // Default to 3001 (standard NexusCRM port)
	}

	// Initialize database connection
	db, err := database.GetInstance()
	if err != nil {
		log.Fatal().Err(err).Msg("Failed to connect to database")
	}
	log.Info().Msg("✅ Database connection established")

	var runtimeOverlay *tenantoverlay.Pack
	if overlayDir := strings.TrimSpace(os.Getenv(controlplane.TenantOverlayDirEnv)); overlayDir != "" {
		runtimeOverlay = tenantoverlay.New(overlayDir)
		if err := runtimeOverlay.RegisterFieldManifest(); err != nil {
			log.Fatal().Err(err).Msg("Failed to register tenant field manifest")
		}
		if err := runtimeOverlay.RegisterRelatedListRules(); err != nil {
			log.Fatal().Err(err).Msg("Failed to register tenant related-list rules")
		}
	}

	// Install the field-provenance classifier before anything that calls
	// ComputeIntrinsic (service manager metadata load, describe emitters,
	// persistence gates). Layer 1d/1e under issue #1582 RFC v1.6 consult
	// this classifier instead of reading stored Source / IsCompound
	// columns; a nil default classifier would produce an all-discovered
	// safe fallback that blocks writes — correct for safety but wrong for
	// normal operation, so install must run before serving.
	if err := bootstrap.InitFieldProvenance(bootstrap.DefaultPlatformStandardFields()); err != nil {
		log.Fatal().Err(err).Msg("Failed to initialize field-provenance classifier")
	}
	log.Info().Msg("✅ Field-provenance classifier initialized")

	if runtimeOverlay != nil {
		if err := runtimeOverlay.RegisterHrefFields(); err != nil {
			log.Fatal().Err(err).Msg("Failed to register tenant href fields")
		}
		log.Info().Msg("✅ Tenant runtime overlay registered")
	}

	// Server is READ-ONLY with respect to metadata. All mutations happen
	// through the control plane (cmd/setup). The server only verifies
	// readiness, loads the service graph, and serves requests.
	skipBootstrap := os.Getenv("SKIP_BOOTSTRAP") == "true"
	if skipBootstrap {
		env := strings.ToLower(os.Getenv("ENVIRONMENT"))
		nexusEnv := strings.ToLower(os.Getenv("NEXUS_ENV"))
		if env == "production" || env == "prod" || env == "staging" ||
			nexusEnv == "production" || nexusEnv == "prod" || nexusEnv == "staging" {
			log.Fatal().Msg("❌ SKIP_BOOTSTRAP=true is not allowed in production/staging")
		}
		log.Warn().Msg("⚠️  Skipping verification (SKIP_BOOTSTRAP=true) — dev mode only")
	}

	if !skipBootstrap {
		if err := controlplane.Verify(context.Background(), db); err != nil {
			log.Fatal().Err(err).Msg("❌ Readiness verification failed — run 'go run ./cmd/setup apply' first")
		}
		if runtimeOverlay != nil {
			if err := runtimeOverlay.Verify(context.Background(), packs.VerifyDeps{DB: db.DB(), DeepChecks: false}); err != nil {
				log.Fatal().Err(err).Msg("❌ Tenant overlay readiness verification failed — run 'go run ./cmd/setup apply-tenant' with the same NEXUSCRM_TENANT_OVERLAY_DIR")
			}
			log.Info().Msg("✅ Tenant overlay readiness verification passed")
		}
		log.Info().Msg("✅ Readiness verification passed")
	}

	// Build service graph with real EventBus (runtime needs live listeners).
	// No EventBus override → NewServiceManager creates a real one with metrics.
	infraDeps := controlplane.BuildInfrastructureDeps(db)
	infraDeps.OnEventDropped = middleware.RecordEventDropped
	infraDeps.OnQueueUtilizationChange = middleware.UpdateEventBusQueueUtilization
	svcMgr, err := services.NewServiceManager(infraDeps)
	if err != nil {
		log.Fatal().Err(err).Msg("❌ Service manager init failed — aborting so pod restarts")
	}
	log.Info().Msg("🔧 Service manager initialized")

	// Load metadata cache (read-only)
	if err := svcMgr.RefreshMetadataCache(context.Background()); err != nil {
		log.Fatal().Err(err).Msg("Failed to refresh metadata cache")
	}
	log.Info().Msg("📦 Metadata cache loaded")

	// Create Gin router with NO default middleware
	router := gin.New()

	// Trust only private/loopback IPs for X-Forwarded-For so that
	// c.ClientIP() returns the real client IP even behind load balancers.
	if err := router.SetTrustedProxies([]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"}); err != nil {
		log.Fatal().Err(err).Msg("Failed to set trusted proxies")
	}

	// 1. Request ID Middleware - MUST be first
	router.Use(middleware.RequestID())

	// 1a. Audit Entrypoint Middleware (C10.5) - stamps REST_API / BULK_API on
	// the request ctx so downstream PersistenceService / emitters attribute
	// AuditEvents correctly instead of falling back to EntrypointUnknown.
	router.Use(audit.AuditEntrypointMiddleware())

	// 1b. Request Capture Middleware (C14, design §14.4/§14.6) - writes an
	// opt-in record to _System_RequestCapture per request so the replay
	// toolchain (C15, scripts/audit-replay/) can reconstruct traffic for
	// behavior-equivalence validation. Gated by NEXUSCRM_REQUEST_CAPTURE=on
	// (default off per §14.11); response bodies are NEVER captured unless
	// both NEXUSCRM_REQUEST_CAPTURE_RESPONSE=on AND the route is registered
	// via audit.EnableResponseCapture(path). DB write failures WARN but
	// never block the request (§14.6).
	router.Use(audit.RequestCaptureMiddleware(audit.RequestCaptureConfig{
		Store: audit.NewSQLRequestCaptureStore(db.DB()),
	}))

	// 2. Custom Logger - Replaced gin.Logger with structured zerolog middleware
	router.Use(middleware.RequestLogger())

	// 2a. Distributed Tracing - Correlates requests across services
	router.Use(middleware.Tracing())

	// 3. Recovery Middleware (Standard)
	router.Use(middleware.Recovery())

	// 3b. Global Error Handler - Translates errors to JSON responses
	router.Use(middleware.GlobalErrorHandler(svcMgr.Logger))

	// 4. Security Headers Middleware
	router.Use(middleware.SecurityHeaders())

	// 5. CORS middleware
	router.Use(middleware.Cors())

	// 5b. CSRF protection — rejects cross-origin state-changing requests
	router.Use(middleware.CSRF())

	// 6. Prometheus Metrics Middleware
	router.Use(middleware.PrometheusMiddleware())

	// 7. Rate Limiting Middleware (100 req/s per IP, burst 200)
	rateLimiter := middleware.DefaultRateLimiter()
	router.Use(rateLimiter.Middleware())

	// Swagger UI
	router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	// Prometheus metrics endpoint
	router.GET("/metrics", gin.WrapH(promhttp.Handler()))

	// Health check
	healthHandler := func(c *gin.Context) {
		sqlDB := db.DB()
		if err := sqlDB.Ping(); err != nil {
			log.Error().Err(err).Msg("Health check failed: Database unreachable")
			c.JSON(503, gin.H{
				"status": "error",
				"error":  "Database unreachable",
			})
			return
		}

		c.JSON(200, gin.H{
			"status": "ok",
			"server": "golang",
			"db":     "connected",
		})
	}
	router.GET("/health", healthHandler)
	router.HEAD("/health", healthHandler)

	// Salesforce instance reverse proxy (no auth required — SF validates its own token)
	rest.RegisterSfInstanceProxy(router)

	// Initialize middleware (before debug routes so requireAuth is available)
	requireAuth := middleware.RequireAuth(svcMgr.Auth)
	permCache := middleware.NewPermissionCache(middleware.DefaultPermCacheTTL)
	permLoader := middleware.PermissionLoader(svcMgr.Permissions, permCache)

	// Debug/pprof endpoints — disabled by default. Expose heap dumps and
	// goroutine stacks which may contain in-memory secrets; opt-in only.
	// Requires authentication even when enabled (C5 fix).
	if os.Getenv("ENABLE_PPROF") == "true" {
		debug := router.Group("/debug/pprof", requireAuth)
		{
			debug.GET("/", gin.WrapF(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				http.Redirect(w, r, "/debug/pprof/", http.StatusMovedPermanently)
			})))
			debug.GET("/goroutine", gin.WrapH(http.DefaultServeMux))
			debug.GET("/heap", gin.WrapH(http.DefaultServeMux))
			debug.GET("/threadcreate", gin.WrapH(http.DefaultServeMux))
			debug.GET("/block", gin.WrapH(http.DefaultServeMux))
			debug.GET("/mutex", gin.WrapH(http.DefaultServeMux))
			debug.GET("/profile", gin.WrapH(http.DefaultServeMux))
			debug.GET("/trace", gin.WrapH(http.DefaultServeMux))
		}
		log.Info().Msg("🔧 Debug/pprof endpoints enabled (ENABLE_PPROF=true, auth required)")
	}

	// Attach the permission cache to the service so object/field permission
	// checks are served from memory instead of hitting the DB per-request.
	svcMgr.Permissions.SetCache(permCache)

	// Initialize custom scope registry (loads from _System_OAuthCustomScope table).
	scopeRegistry := middleware.NewScopeRegistry()
	if err := scopeRegistry.LoadFromDB(db.DB()); err != nil {
		log.Warn().Err(err).Msg("Failed to load custom scopes")
	}

	// Register all REST routes (handlers, route groups, streaming, etc.)
	deps := registerRoutes(router, svcMgr, db, requireAuth, permLoader, permCache, scopeRegistry)

	// Start background workers
	svcMgr.StartBackgroundWorkers()
	log.Info().Msg("⚙️  Background workers started (Outbox, Scheduler, BulkProcessor)")

	// C10.5: Audit V2 OutboxWorker startup.
	//
	// The worker drains _System_AuditOutbox rows into field-level
	// _System_AuditLog rows. Head rows land synchronously inside the
	// business tx, so C11's <Object>History read surface works even when
	// this worker is NOT running — outbox rows accumulate harmlessly and
	// are drained when the worker comes online.
	//
	// Gated by AUDIT_V2_WORKER_ENABLED because the MySQL-backed
	// OutboxStore implementation has not landed yet (tracked as follow-up
	// to C10.5). When the store exists, the operator sets the env var and
	// restarts; the worker will then run for the lifetime of the process
	// and stop on SIGINT/SIGTERM via auditWorkerCtx cancellation below.
	auditWorkerCtx, auditWorkerCancel := context.WithCancel(context.Background())
	defer auditWorkerCancel()
	if os.Getenv("AUDIT_V2_WORKER_ENABLED") == "true" {
		store := audit.ResolveOutboxStore(db.DB())
		if store == nil {
			log.Warn().Msg("⚠️  AUDIT_V2_WORKER_ENABLED=true but no OutboxStore implementation available yet — worker NOT started")
		} else {
			worker := audit.NewOutboxWorker(store, audit.OutboxWorkerConfig{
				HolderID: "audit-outbox-" + port,
			})
			go worker.Run(auditWorkerCtx)
			log.Info().Msg("📝 Audit V2 outbox worker started")
		}
	} else {
		log.Info().Msg("📝 Audit V2 outbox worker disabled (set AUDIT_V2_WORKER_ENABLED=true to enable)")
	}

	// Start session cleanup job (runs every hour, deletes sessions older than 7 days)
	sessionCleaner := auth.NewSessionCleaner(
		infraDeps.SessionRepo,
		1*time.Hour,
		7*24*time.Hour,
	)
	sessionCleaner.Start()
	log.Info().Msg("🧹 Session cleanup job started")

	// Start server
	log.Info().Msg("═══════════════════════════════════════════════════════════════════════════")
	log.Info().Msg("🚀 NexusCRM Golang Backend Started Successfully")
	log.Info().Msg("═══════════════════════════════════════════════════════════════════════════")
	log.Info().Str("port", port).Msg("Server Routes Configured")

	// Create HTTP Server
	srv := &http.Server{
		Addr:    "0.0.0.0:" + port,
		Handler: router,
	}

	// Start server in a goroutine
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatal().Err(err).Msg("Failed to start server")
		}
	}()

	// Wait for interrupt signal to gracefully shutdown the server with a timeout of 5 seconds.
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Info().Msg("Shutting down server...")

	// Stop background workers
	svcMgr.StopBackgroundWorkers()
	log.Info().Msg("🛑 Background workers stopped")
	auditWorkerCancel()
	log.Info().Msg("🛑 Audit V2 outbox worker signalled to stop")
	sessionCleaner.Stop()
	log.Info().Msg("🛑 Session cleanup job stopped")
	deps.eventBridge.Stop()
	log.Info().Msg("🛑 Event bridge stopped")
	deps.sessionManager.Stop()
	log.Info().Msg("🛑 Session manager stopped")
	deps.uiAPISvc.Close()
	log.Info().Msg("🛑 UI API service stopped")

	contextCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(contextCtx); err != nil {
		log.Fatal().Err(err).Msg("Server forced to shutdown")
	}

	log.Info().Msg("Server exiting")
}
