package main

import (
	"context"
	"database/sql"
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	_ "github.com/lib/pq"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"

	pb "github.com/rwadurian/mpc-system/api/grpc/coordinator/v1"
	"github.com/rwadurian/mpc-system/pkg/config"
	"github.com/rwadurian/mpc-system/pkg/jwt"
	"github.com/rwadurian/mpc-system/pkg/logger"
	"github.com/rwadurian/mpc-system/pkg/middleware"
	grpcadapter "github.com/rwadurian/mpc-system/services/session-coordinator/adapters/input/grpc"
	httphandler "github.com/rwadurian/mpc-system/services/session-coordinator/adapters/input/http"
	grpcclient "github.com/rwadurian/mpc-system/services/session-coordinator/adapters/output/grpc"
	httpclient "github.com/rwadurian/mpc-system/services/session-coordinator/adapters/output/http"
	"github.com/rwadurian/mpc-system/services/session-coordinator/adapters/output/memory"
	"github.com/rwadurian/mpc-system/services/session-coordinator/adapters/output/notification"
	"github.com/rwadurian/mpc-system/services/session-coordinator/adapters/output/postgres"
	"github.com/rwadurian/mpc-system/services/session-coordinator/application/use_cases"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/repositories"
	"github.com/rwadurian/mpc-system/services/session-coordinator/infrastructure/discovery"
	"go.uber.org/zap"
)

func main() {
	// Parse flags
	configPath := flag.String("config", "", "Path to config file")
	flag.Parse()

	// Load configuration
	cfg, err := config.Load(*configPath)
	if err != nil {
		fmt.Printf("Failed to load config: %v\n", err)
		os.Exit(1)
	}

	// Initialize logger
	fmt.Printf("DEBUG: Logger level from config: '%s'\n", cfg.Logger.Level)
	if err := logger.Init(&logger.Config{
		Level:    cfg.Logger.Level,
		Encoding: cfg.Logger.Encoding,
	}); err != nil {
		fmt.Printf("Failed to initialize logger: %v\n", err)
		os.Exit(1)
	}
	defer logger.Sync()

	// Test debug logging
	logger.Debug("TEST: This is a DEBUG level log - if you see this, debug logging works!")
	logger.Info("Starting Session Coordinator Service",
		zap.String("environment", cfg.Server.Environment),
		zap.Int("grpc_port", cfg.Server.GRPCPort),
		zap.Int("http_port", cfg.Server.HTTPPort))

	// Initialize database connection
	db, err := initDatabase(cfg.Database)
	if err != nil {
		logger.Fatal("Failed to connect to database", zap.Error(err))
	}
	defer db.Close()

	// Initialize repositories and adapters
	sessionRepo := postgres.NewSessionPostgresRepo(db)
	messageRepo := postgres.NewMessagePostgresRepo(db)

	// Initialize in-memory event publisher (replaces RabbitMQ)
	eventPublisher := memory.NewEventPublisherAdapter()
	defer eventPublisher.Close()

	// Initialize JWT service
	jwtService := jwt.NewJWTService(
		cfg.JWT.SecretKey,
		cfg.JWT.Issuer,
		cfg.JWT.TokenExpiry,
		cfg.JWT.RefreshExpiry,
	)

	// Initialize Message Router gRPC client for event publishing and party discovery
	routerAddr := os.Getenv("MESSAGE_ROUTER_ADDR")
	if routerAddr == "" {
		routerAddr = "localhost:9092" // Default for local development
	}
	messageRouterClient, err := grpcclient.NewMessageRouterClient(routerAddr)
	if err != nil {
		logger.Fatal("Failed to connect to Message Router", zap.Error(err))
	}
	defer messageRouterClient.Close()

	// Initialize party discovery using Message Router (decentralized approach)
	// This replaces K8s-based discovery and works in any environment
	partyPool := discovery.NewMessageRouterPartyDiscovery(messageRouterClient, logger.Log)
	logger.Info("Party discovery initialized using Message Router")

	// Initialize Account Service HTTP client
	accountServiceAddr := os.Getenv("ACCOUNT_SERVICE_ADDR")
	if accountServiceAddr == "" {
		accountServiceAddr = "http://localhost:8081" // Default for local development
	}
	accountServiceClient := httpclient.NewAccountServiceClient(accountServiceAddr)
	logger.Info("Account Service client initialized", zap.String("address", accountServiceAddr))

	// Initialize Notification Service (stub implementation - logs but doesn't send)
	// Replace with real implementation (email/SMS/push) when notification providers are configured
	notificationService := notification.NewStubNotificationService()
	logger.Info("Notification Service initialized (stub mode - logging only)")

	// Initialize use cases
	createSessionUC := use_cases.NewCreateSessionUseCaseWithNotification(sessionRepo, jwtService, eventPublisher, partyPool, messageRouterClient, notificationService)
	joinSessionUC := use_cases.NewJoinSessionUseCase(sessionRepo, jwtService, eventPublisher, messageRouterClient)
	getSessionStatusUC := use_cases.NewGetSessionStatusUseCase(sessionRepo)
	reportCompletionUC := use_cases.NewReportCompletionUseCase(sessionRepo, eventPublisher, accountServiceClient)
	closeSessionUC := use_cases.NewCloseSessionUseCase(sessionRepo, messageRepo, eventPublisher)
	expireSessionsUC := use_cases.NewExpireSessionsUseCase(sessionRepo, eventPublisher)
	checkPartyTimeoutsUC := use_cases.NewCheckPartyTimeoutsUseCase(sessionRepo, eventPublisher, 2*time.Minute)

	// Start session expiration background job
	go runSessionExpiration(expireSessionsUC)

	// Start party timeout checking background job
	go runPartyTimeoutCheck(checkPartyTimeoutsUC)

	// Create shutdown context
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Start servers
	errChan := make(chan error, 2)

	// Start gRPC server
	go func() {
		if err := startGRPCServer(
			cfg,
			createSessionUC,
			joinSessionUC,
			getSessionStatusUC,
			reportCompletionUC,
			closeSessionUC,
			sessionRepo,
		); err != nil {
			errChan <- fmt.Errorf("gRPC server error: %w", err)
		}
	}()

	// Start HTTP server
	go func() {
		if err := startHTTPServer(
			cfg,
			jwtService,
			createSessionUC,
			joinSessionUC,
			getSessionStatusUC,
			reportCompletionUC,
			closeSessionUC,
			sessionRepo,
		); err != nil {
			errChan <- fmt.Errorf("HTTP server error: %w", err)
		}
	}()

	// Wait for shutdown signal
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	select {
	case sig := <-sigChan:
		logger.Info("Received shutdown signal", zap.String("signal", sig.String()))
	case err := <-errChan:
		logger.Error("Server error", zap.Error(err))
	}

	// Graceful shutdown
	logger.Info("Shutting down...")
	cancel()

	// Give services time to shutdown gracefully
	time.Sleep(5 * time.Second)
	logger.Info("Shutdown complete")

	_ = ctx
}

func initDatabase(cfg config.DatabaseConfig) (*sql.DB, error) {
	const maxRetries = 10
	const retryDelay = 2 * time.Second

	var db *sql.DB
	var err error

	for i := 0; i < maxRetries; i++ {
		db, err = sql.Open("postgres", cfg.DSN())
		if err != nil {
			logger.Warn("Failed to open database connection, retrying...",
				zap.Int("attempt", i+1),
				zap.Int("max_retries", maxRetries),
				zap.Error(err))
			time.Sleep(retryDelay * time.Duration(i+1))
			continue
		}

		db.SetMaxOpenConns(cfg.MaxOpenConns)
		db.SetMaxIdleConns(cfg.MaxIdleConns)
		db.SetConnMaxLifetime(cfg.ConnMaxLife)

		// Test connection
		if err = db.Ping(); err != nil {
			logger.Warn("Failed to ping database, retrying...",
				zap.Int("attempt", i+1),
				zap.Int("max_retries", maxRetries),
				zap.Error(err))
			db.Close()
			time.Sleep(retryDelay * time.Duration(i+1))
			continue
		}

		logger.Info("Connected to PostgreSQL")
		return db, nil
	}

	return nil, fmt.Errorf("failed to connect to database after %d retries: %w", maxRetries, err)
}

func startGRPCServer(
	cfg *config.Config,
	createSessionUC *use_cases.CreateSessionUseCase,
	joinSessionUC *use_cases.JoinSessionUseCase,
	getSessionStatusUC *use_cases.GetSessionStatusUseCase,
	reportCompletionUC *use_cases.ReportCompletionUseCase,
	closeSessionUC *use_cases.CloseSessionUseCase,
	sessionRepo repositories.SessionRepository,
) error {
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Server.GRPCPort))
	if err != nil {
		return err
	}

	grpcServer := grpc.NewServer()

	// Create and register the session coordinator gRPC handler
	sessionCoordinatorServer := grpcadapter.NewSessionCoordinatorServer(
		createSessionUC,
		joinSessionUC,
		getSessionStatusUC,
		reportCompletionUC,
		closeSessionUC,
		sessionRepo,
	)
	pb.RegisterSessionCoordinatorServer(grpcServer, sessionCoordinatorServer)

	// Enable reflection for debugging
	reflection.Register(grpcServer)

	logger.Info("Starting gRPC server", zap.Int("port", cfg.Server.GRPCPort))
	return grpcServer.Serve(listener)
}

func startHTTPServer(
	cfg *config.Config,
	jwtService *jwt.JWTService,
	createSessionUC *use_cases.CreateSessionUseCase,
	joinSessionUC *use_cases.JoinSessionUseCase,
	getSessionStatusUC *use_cases.GetSessionStatusUseCase,
	reportCompletionUC *use_cases.ReportCompletionUseCase,
	closeSessionUC *use_cases.CloseSessionUseCase,
	sessionRepo repositories.SessionRepository,
) error {
	// Set Gin mode
	if cfg.Server.Environment == "production" {
		gin.SetMode(gin.ReleaseMode)
	}

	router := gin.New()
	router.Use(gin.Recovery())
	router.Use(gin.Logger())

	// Apply security headers middleware
	router.Use(middleware.SecureHeaders())

	// Apply CORS middleware
	allowedOrigins := []string{}
	if origins := os.Getenv("CORS_ALLOWED_ORIGINS"); origins != "" {
		allowedOrigins = strings.Split(origins, ",")
	}
	if cfg.Server.Environment != "production" {
		router.Use(middleware.AllowAllCORS())
	} else if len(allowedOrigins) > 0 {
		router.Use(middleware.CORS(middleware.CORSConfig{
			AllowOrigins:     allowedOrigins,
			AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
			AllowHeaders:     []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With"},
			ExposeHeaders:    []string{"Content-Length", "X-Request-ID"},
			AllowCredentials: true,
			MaxAge:           86400,
		}))
	}

	// Create HTTP handler
	httpHandler := httphandler.NewSessionHTTPHandler(
		createSessionUC,
		joinSessionUC,
		getSessionStatusUC,
		reportCompletionUC,
		closeSessionUC,
		sessionRepo,
	)

	// Health check (public)
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status":  "healthy",
			"service": "session-coordinator",
		})
	})

	// Configure authentication middleware
	// Session Coordinator HTTP API requires authentication
	// Note: gRPC endpoints have separate authentication (service-to-service)
	authConfig := middleware.AuthConfig{
		JWTService: jwtService,
		SkipPaths: []string{
			"/health",
		},
		AllowAnonymous: false,
	}

	// API routes with authentication
	api := router.Group("/api/v1")
	api.Use(middleware.BearerAuth(authConfig))
	httpHandler.RegisterRoutes(api)

	logger.Info("Starting HTTP server",
		zap.Int("port", cfg.Server.HTTPPort),
		zap.String("environment", cfg.Server.Environment))
	return router.Run(fmt.Sprintf(":%d", cfg.Server.HTTPPort))
}

func runSessionExpiration(expireSessionsUC *use_cases.ExpireSessionsUseCase) {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		count, err := expireSessionsUC.Execute(ctx)
		cancel()

		if err != nil {
			logger.Error("Failed to expire sessions", zap.Error(err))
		} else if count > 0 {
			logger.Info("Expired stale sessions", zap.Int("count", count))
		}
	}
}

func runPartyTimeoutCheck(checkPartyTimeoutsUC *use_cases.CheckPartyTimeoutsUseCase) {
	// Check for party timeouts every 30 seconds
	ticker := time.NewTicker(30 * time.Second)
	defer ticker.Stop()

	logger.Info("Party timeout checker started",
		zap.Duration("check_interval", 30*time.Second),
		zap.Duration("inactivity_timeout", checkPartyTimeoutsUC.GetInactivityTimeout()))

	for range ticker.C {
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		result, err := checkPartyTimeoutsUC.Execute(ctx)
		cancel()

		if err != nil {
			logger.Error("Failed to check party timeouts", zap.Error(err))
		} else if result.PartiesTimedOut > 0 {
			logger.Warn("Detected timed-out parties",
				zap.Int("sessions_checked", result.SessionsChecked),
				zap.Int("parties_timed_out", result.PartiesTimedOut),
				zap.Int("sessions_failed", result.SessionsFailedDueToTimeout))
		}
	}
}
