package main

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

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	pgdriver "gorm.io/driver/postgres"
	"gorm.io/gorm"
	gormlogger "gorm.io/gorm/logger"

	appservice "github.com/genex/chain-indexer/internal/application/service"
	"github.com/genex/chain-indexer/internal/infrastructure/kafka"
	"github.com/genex/chain-indexer/internal/infrastructure/postgres"
	"github.com/genex/chain-indexer/internal/infrastructure/rpc"
	"github.com/genex/chain-indexer/internal/interface/http/handler"
	"github.com/genex/chain-indexer/internal/interface/http/middleware"
)

func main() {
	logger, _ := zap.NewProduction()
	defer logger.Sync()

	port := os.Getenv("PORT")
	if port == "" {
		port = "3009"
	}

	// ── Infrastructure layer ────────────────────────────────────────────
	db := mustInitDB(logger)

	blockRepo := postgres.NewPostgresBlockRepository(db)
	txRepo := postgres.NewPostgresTransactionRepository(db)

	eventPublisher := mustInitKafka(logger)
	defer eventPublisher.Close()

	chainClient := mustInitChainClient(logger)
	defer chainClient.Close()

	// ── Application layer ───────────────────────────────────────────────
	indexerSvc := appservice.NewIndexerService(logger, blockRepo, txRepo, eventPublisher, chainClient)
	indexerSvc.Start()

	// ── Interface layer (HTTP) ──────────────────────────────────────────
	r := gin.New()
	r.Use(gin.Recovery())

	// Health checks
	r.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{"status": "ok", "service": "chain-indexer", "lastHeight": indexerSvc.GetLastHeight()})
	})
	r.GET("/health/ready", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ready"}) })
	r.GET("/health/live", func(c *gin.Context) { c.JSON(200, gin.H{"status": "alive"}) })

	// Public API routes
	api := r.Group("/api/v1/chain")
	api.GET("/blocks", func(c *gin.Context) {
		blocks := indexerSvc.GetRecentBlocks(20)
		c.JSON(200, gin.H{"code": 0, "data": gin.H{"blocks": blocks, "lastHeight": indexerSvc.GetLastHeight()}})
	})
	api.GET("/status", func(c *gin.Context) {
		c.JSON(200, gin.H{"code": 0, "data": gin.H{
			"lastHeight":  indexerSvc.GetLastHeight(),
			"chainHeight": indexerSvc.GetChainHeight(),
			"syncing":     !indexerSvc.IsSynced(),
		}})
	})

	// Admin routes (require JWT + admin role)
	adminChainHandler := handler.NewAdminChainHandler(indexerSvc)
	admin := r.Group("/api/v1/admin/chain")
	admin.Use(middleware.JWTAuth(), middleware.RequireAdmin())
	{
		admin.GET("/contracts", adminChainHandler.GetContracts)
		admin.GET("/events", adminChainHandler.GetEvents)
		admin.GET("/gas-monitor", adminChainHandler.GetGasMonitor)
		admin.GET("/stats", adminChainHandler.GetChainStats)
	}

	server := &http.Server{Addr: ":" + port, Handler: r, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second}

	go func() {
		logger.Info("Chain Indexer starting", zap.String("port", port))
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Fatal("Failed", zap.Error(err))
		}
	}()

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

	indexerSvc.Stop()
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	server.Shutdown(ctx)
	logger.Info("Chain Indexer stopped")
}

func mustInitDB(logger *zap.Logger) *gorm.DB {
	host := getEnv("DB_HOST", "localhost")
	dbPort := getEnv("DB_PORT", "5432")
	user := getEnv("DB_USERNAME", "genex")
	pass := getEnv("DB_PASSWORD", "genex_dev_password")
	name := getEnv("DB_NAME", "genex")

	dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
		host, dbPort, user, pass, name)

	db, err := gorm.Open(pgdriver.Open(dsn), &gorm.Config{
		Logger: gormlogger.Default.LogMode(gormlogger.Warn),
	})
	if err != nil {
		logger.Fatal("Failed to connect to PostgreSQL", zap.Error(err))
	}

	sqlDB, _ := db.DB()
	sqlDB.SetMaxOpenConns(20)
	sqlDB.SetMaxIdleConns(5)
	sqlDB.SetConnMaxLifetime(30 * time.Minute)

	logger.Info("PostgreSQL connected", zap.String("host", host), zap.String("db", name))
	return db
}

func mustInitKafka(logger *zap.Logger) *kafka.KafkaEventPublisher {
	brokersEnv := getEnv("KAFKA_BROKERS", "localhost:9092")
	brokers := strings.Split(brokersEnv, ",")

	publisher, err := kafka.NewKafkaEventPublisher(brokers)
	if err != nil {
		logger.Fatal("Failed to connect to Kafka", zap.Error(err))
	}

	logger.Info("Kafka producer connected", zap.Strings("brokers", brokers))
	return publisher
}

func mustInitChainClient(logger *zap.Logger) *rpc.EthChainClient {
	rpcURL := getEnv("RPC_URL", "http://localhost:8545")

	client, err := rpc.NewEthChainClient(rpcURL, logger)
	if err != nil {
		logger.Fatal("Failed to connect to blockchain node", zap.String("rpcURL", rpcURL), zap.Error(err))
	}

	return client
}

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