package main

import (
	"context"
	"database/sql"
	"errors"
	"flag"
	"log"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"hyapp-admin-server/internal/cache"
	"hyapp-admin-server/internal/config"
	"hyapp-admin-server/internal/integration/activityclient"
	"hyapp-admin-server/internal/integration/gameclient"
	"hyapp-admin-server/internal/integration/robotclient"
	"hyapp-admin-server/internal/integration/roomclient"
	"hyapp-admin-server/internal/integration/userclient"
	"hyapp-admin-server/internal/integration/walletclient"
	jobrunner "hyapp-admin-server/internal/job"
	"hyapp-admin-server/internal/migration"
	achievementconfigmodule "hyapp-admin-server/internal/modules/achievementconfig"
	adminusermodule "hyapp-admin-server/internal/modules/adminuser"
	agencyopeningmodule "hyapp-admin-server/internal/modules/agencyopening"
	appconfigmodule "hyapp-admin-server/internal/modules/appconfig"
	appregistrymodule "hyapp-admin-server/internal/modules/appregistry"
	appusermodule "hyapp-admin-server/internal/modules/appuser"
	auditmodule "hyapp-admin-server/internal/modules/audit"
	authmodule "hyapp-admin-server/internal/modules/auth"
	coinledgermodule "hyapp-admin-server/internal/modules/coinledger"
	countryregionmodule "hyapp-admin-server/internal/modules/countryregion"
	cprelationmodule "hyapp-admin-server/internal/modules/cprelation"
	cpweeklyrankmodule "hyapp-admin-server/internal/modules/cpweeklyrank"
	cumulativerechargerewardmodule "hyapp-admin-server/internal/modules/cumulativerechargereward"
	dailytaskmodule "hyapp-admin-server/internal/modules/dailytask"
	dashboardmodule "hyapp-admin-server/internal/modules/dashboard"
	firstrechargerewardmodule "hyapp-admin-server/internal/modules/firstrechargereward"
	fullservernoticemodule "hyapp-admin-server/internal/modules/fullservernotice"
	gamemanagementmodule "hyapp-admin-server/internal/modules/gamemanagement"
	giftdiamondmodule "hyapp-admin-server/internal/modules/giftdiamond"
	healthmodule "hyapp-admin-server/internal/modules/health"
	hostagencypolicymodule "hyapp-admin-server/internal/modules/hostagencypolicy"
	hostorgmodule "hyapp-admin-server/internal/modules/hostorg"
	hostsalarysettlementmodule "hyapp-admin-server/internal/modules/hostsalarysettlement"
	inviteactivityrewardmodule "hyapp-admin-server/internal/modules/inviteactivityreward"
	jobmodule "hyapp-admin-server/internal/modules/job"
	levelconfigmodule "hyapp-admin-server/internal/modules/levelconfig"
	luckygiftmodule "hyapp-admin-server/internal/modules/luckygift"
	menumodule "hyapp-admin-server/internal/modules/menu"
	paymentmodule "hyapp-admin-server/internal/modules/payment"
	prettyidmodule "hyapp-admin-server/internal/modules/prettyid"
	rbacmodule "hyapp-admin-server/internal/modules/rbac"
	redpacketmodule "hyapp-admin-server/internal/modules/redpacket"
	regionblockmodule "hyapp-admin-server/internal/modules/regionblock"
	registrationrewardmodule "hyapp-admin-server/internal/modules/registrationreward"
	reportmodule "hyapp-admin-server/internal/modules/report"
	resourcemodule "hyapp-admin-server/internal/modules/resource"
	riskconfigmodule "hyapp-admin-server/internal/modules/riskconfig"
	roomadminmodule "hyapp-admin-server/internal/modules/roomadmin"
	roomrocketmodule "hyapp-admin-server/internal/modules/roomrocket"
	roomturnoverrewardmodule "hyapp-admin-server/internal/modules/roomturnoverreward"
	searchmodule "hyapp-admin-server/internal/modules/search"
	sevendaycheckinmodule "hyapp-admin-server/internal/modules/sevendaycheckin"
	teamsalarypolicymodule "hyapp-admin-server/internal/modules/teamsalarypolicy"
	teamsalarysettlementmodule "hyapp-admin-server/internal/modules/teamsalarysettlement"
	uploadmodule "hyapp-admin-server/internal/modules/upload"
	userleaderboardmodule "hyapp-admin-server/internal/modules/userleaderboard"
	vipconfigmodule "hyapp-admin-server/internal/modules/vipconfig"
	weeklystarmodule "hyapp-admin-server/internal/modules/weeklystar"
	wheelmodule "hyapp-admin-server/internal/modules/wheel"
	"hyapp-admin-server/internal/platform/logging"
	"hyapp-admin-server/internal/platform/tencentcos"
	"hyapp-admin-server/internal/repository"
	"hyapp-admin-server/internal/router"
	"hyapp-admin-server/internal/service"

	mysqlDriver "github.com/go-sql-driver/mysql"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/keepalive"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	walletv1 "hyapp.local/api/proto/wallet/v1"
)

func main() {
	configPath := flag.String("config", "configs/config.yaml", "path to admin-server config")
	runBootstrap := flag.Bool("bootstrap", false, "run configured bootstrap seed before starting")
	flag.Parse()

	cfg, err := config.Load(*configPath)
	if err != nil {
		log.Fatal(err)
	}
	if err := logging.Init(cfg.ServiceName, cfg.Environment, cfg.NodeID, cfg.Log); err != nil {
		log.Fatal(err)
	}

	if err := ensureDatabase(cfg.MySQLDSN); err != nil {
		fatalRuntime("ensure_mysql_database_failed", err)
	}

	dbLogger := logger.New(log.New(logging.StdWriter(), "", 0), logger.Config{
		LogLevel:                  logger.Warn,
		IgnoreRecordNotFoundError: true,
	})
	db, err := gorm.Open(mysql.Open(cfg.MySQLDSN), &gorm.Config{
		Logger: dbLogger,
		NowFunc: func() time.Time {
			return time.Now().UTC()
		},
	})
	if err != nil {
		fatalRuntime("connect_mysql_failed", err)
	}
	sqlDB, err := db.DB()
	if err != nil {
		fatalRuntime("mysql_db_handle_failed", err)
	}
	if cfg.Migrations.Enabled {
		if err := migration.ApplyWithOptions(context.Background(), sqlDB, cfg.Migrations.Dir, migration.Options{
			AllowChecksumRepair: cfg.Migrations.AllowChecksumRepair,
		}); err != nil {
			fatalRuntime("apply_migrations_failed", err)
		}
	}
	redisClient, err := cache.NewRedis(context.Background(), cfg.Redis)
	if err != nil {
		fatalRuntime("connect_redis_failed", err)
	}
	if redisClient != nil {
		defer redisClient.Close()
	}

	userDB, err := sql.Open("mysql", cfg.UserMySQLDSN)
	if err != nil {
		fatalRuntime("connect_user_mysql_failed", err)
	}
	if err := userDB.PingContext(context.Background()); err != nil {
		fatalRuntime("ping_user_mysql_failed", err)
	}
	defer userDB.Close()

	walletDB, err := sql.Open("mysql", cfg.WalletMySQLDSN)
	if err != nil {
		fatalRuntime("connect_wallet_mysql_failed", err)
	}
	if err := walletDB.PingContext(context.Background()); err != nil {
		fatalRuntime("ping_wallet_mysql_failed", err)
	}
	defer walletDB.Close()

	var robotProfileSource gamemanagementmodule.RobotProfileSource
	if strings.TrimSpace(cfg.RobotProfileSource.MySQLDSN) != "" {
		robotProfileDB, err := sql.Open("mysql", cfg.RobotProfileSource.MySQLDSN)
		if err != nil {
			fatalRuntime("connect_robot_profile_mysql_failed", err)
		}
		robotProfileCtx, robotProfileCancel := context.WithTimeout(context.Background(), cfg.RobotProfileSource.RequestTimeout)
		if err := robotProfileDB.PingContext(robotProfileCtx); err != nil {
			robotProfileCancel()
			fatalRuntime("ping_robot_profile_mysql_failed", err)
		}
		robotProfileCancel()
		defer robotProfileDB.Close()
		robotProfileSource = gamemanagementmodule.NewLikeiRobotProfileSource(robotProfileDB)
	}

	store := repository.New(db)
	if cfg.MySQLAutoMigrate {
		if err := store.AutoMigrate(); err != nil {
			fatalRuntime("auto_migrate_failed", err)
		}
	}
	if *runBootstrap || cfg.Bootstrap.Enabled {
		if err := store.Seed(cfg); err != nil {
			fatalRuntime("seed_database_failed", err)
		}
	}

	userConn, err := dialBackendGRPC(cfg.UserService.Addr)
	if err != nil {
		fatalRuntime("connect_user_service_failed", err)
	}
	defer userConn.Close()

	walletConn, err := dialBackendGRPC(cfg.WalletService.Addr)
	if err != nil {
		fatalRuntime("connect_wallet_service_failed", err)
	}
	defer walletConn.Close()

	roomConn, err := dialBackendGRPC(cfg.RoomService.Addr)
	if err != nil {
		fatalRuntime("connect_room_service_failed", err)
	}
	defer roomConn.Close()
	roomClient := roomclient.NewGRPC(roomConn)

	robotConn, err := dialBackendGRPC(cfg.RobotService.Addr)
	if err != nil {
		fatalRuntime("connect_robot_service_failed", err)
	}
	defer robotConn.Close()
	robotClient := robotclient.NewGRPC(robotConn)

	activityConn, err := dialBackendGRPC(cfg.ActivityService.Addr)
	if err != nil {
		fatalRuntime("connect_activity_service_failed", err)
	}
	defer activityConn.Close()

	gameConn, err := dialBackendGRPC(cfg.GameService.Addr)
	if err != nil {
		fatalRuntime("connect_game_service_failed", err)
	}
	defer gameConn.Close()

	auth := service.NewAuthService(cfg.JWTSecret, cfg.AccessTokenTTL)
	var runner *jobrunner.Runner
	var jobStatus healthmodule.JobStatusProvider
	if cfg.Jobs.Enabled {
		runner = jobrunner.NewRunner(store, redisClient, cfg)
		runner.Start()
		defer runner.Close()
		jobStatus = runner
	}

	auditHandler := auditmodule.New(store)
	teamSalarySettlementHandler := teamsalarysettlementmodule.New(sqlDB, walletDB, userDB, auditHandler)
	if cfg.Jobs.Enabled {
		teamSalaryRunner := teamsalarysettlementmodule.NewAutoRunner(teamSalarySettlementHandler.Service(), "lalu", cfg.NodeID)
		teamSalaryRunner.Start()
		defer teamSalaryRunner.Close()
	}
	var objectUploader uploadmodule.ObjectUploader
	if cfg.TencentCOS.Enabled {
		uploader, err := tencentcos.NewUploader(tencentcos.Config{
			SecretID:  cfg.TencentCOS.SecretID,
			SecretKey: cfg.TencentCOS.SecretKey,
			Bucket:    cfg.TencentCOS.BucketName,
			Region:    cfg.TencentCOS.Region,
			AccessURL: cfg.TencentCOS.AccessURL,
		}, nil)
		if err != nil {
			fatalRuntime("create_tencent_cos_uploader_failed", err)
		}
		objectUploader = uploader
	}
	handlers := router.Handlers{
		Audit:               auditHandler,
		Auth:                authmodule.New(store, auth, auditHandler, cfg),
		AdminUser:           adminusermodule.New(store, cfg, auditHandler),
		AgencyOpening:       agencyopeningmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
		AchievementConfig:   achievementconfigmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
		AppConfig:           appconfigmodule.New(store, auditHandler),
		AppRegistry:         appregistrymodule.New(userDB),
		AppUser:             appusermodule.New(userclient.NewGRPC(userConn), activityclient.NewGRPC(activityConn), sqlDB, userDB, walletDB, cfg, auditHandler),
		CoinLedger:          coinledgermodule.New(userDB, walletDB, sqlDB, walletclient.NewGRPC(walletConn), auditHandler),
		CountryRegion:       countryregionmodule.New(userclient.NewGRPC(userConn), userDB, cfg, auditHandler),
		CPRelation:          cprelationmodule.NewWithActivity(userDB, activityclient.NewGRPC(activityConn), auditHandler),
		CPWeeklyRank:        cpweeklyrankmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
		CumulativeRecharge:  cumulativerechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
		DailyTask:           dailytaskmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
		Dashboard:           dashboardmodule.New(store, cfg, userclient.NewGRPC(userConn)),
		FirstRechargeReward: firstrechargerewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
		FullServerNotice:    fullservernoticemodule.New(activityclient.NewGRPC(activityConn), auditHandler),
		Game: gamemanagementmodule.New(
			gameclient.NewGRPC(gameConn),
			userclient.NewGRPC(userConn),
			auditHandler,
			gamemanagementmodule.WithRobotProfileSource(robotProfileSource),
			gamemanagementmodule.WithRobotAppearanceServices(walletclient.NewGRPC(walletConn), activityclient.NewGRPC(activityConn)),
		),
		GiftDiamond:          giftdiamondmodule.New(walletDB, auditHandler),
		Health:               healthmodule.New(store, redisClient, jobStatus),
		HostAgencyPolicy:     hostagencypolicymodule.New(store, walletDB, auditHandler),
		HostOrg:              hostorgmodule.New(userclient.NewGRPC(userConn), walletclient.NewGRPC(walletConn), userDB, walletDB, sqlDB, auditHandler),
		HostSalarySettlement: hostsalarysettlementmodule.New(walletDB, userDB, walletv1.NewWalletCronServiceClient(walletConn), cfg.WalletService.RequestTimeout, auditHandler),
		InviteActivityReward: inviteactivityrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
		Job:                  jobmodule.New(store, cfg, auditHandler),
		LevelConfig:          levelconfigmodule.New(activityclient.NewGRPC(activityConn), walletclient.NewGRPC(walletConn), auditHandler),
		LuckyGift:            luckygiftmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler),
		Menu:                 menumodule.New(store, auditHandler),
		Payment:              paymentmodule.New(walletclient.NewGRPC(walletConn), userDB, walletDB, store, auditHandler),
		PrettyID:             prettyidmodule.New(userclient.NewGRPC(userConn), auditHandler),
		RBAC:                 rbacmodule.New(store, auditHandler),
		RedPacket:            redpacketmodule.New(walletclient.NewGRPC(walletConn), auditHandler),
		Report:               reportmodule.New(userDB, roomClient),
		RegistrationReward:   registrationrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
		RegionBlock:          regionblockmodule.New(userDB, userclient.NewGRPC(userConn), auditHandler),
		Resource:             resourcemodule.New(walletclient.NewGRPC(walletConn), store, userDB, cfg.WalletService.RequestTimeout, auditHandler),
		RiskConfig:           riskconfigmodule.New(userclient.NewGRPC(userConn), auditHandler),
		RoomAdmin:            roomadminmodule.New(userDB, store, roomClient, robotClient, auditHandler),
		RoomRocket:           roomrocketmodule.New(roomClient, auditHandler),
		RoomTurnoverReward:   roomturnoverrewardmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
		Search:               searchmodule.New(store),
		SevenDayCheckIn:      sevendaycheckinmodule.New(activityclient.NewGRPC(activityConn), userDB, auditHandler),
		TeamSalaryPolicy:     teamsalarypolicymodule.New(store, auditHandler),
		TeamSalarySettlement: teamSalarySettlementHandler,
		Upload:               uploadmodule.New(objectUploader, cfg.TencentCOS.ObjectPrefix, auditHandler),
		UserLeaderboard:      userleaderboardmodule.New(walletDB, userDB, roomClient),
		VIPConfig:            vipconfigmodule.New(walletclient.NewGRPC(walletConn), auditHandler),
		WeeklyStar:           weeklystarmodule.New(activityclient.NewGRPC(activityConn), auditHandler),
		Wheel:                wheelmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler),
	}
	engine := router.New(cfg, auth, handlers)

	server := &http.Server{
		Addr:              cfg.HTTPAddr,
		Handler:           engine,
		ReadHeaderTimeout: 5 * time.Second,
	}

	go func() {
		slog.Info("service_listening", "addr", cfg.HTTPAddr)
		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			fatalRuntime("listen_failed", 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 := server.Shutdown(ctx); err != nil {
		slog.Error("shutdown_failed", "error", err.Error())
	}
}

func fatalRuntime(msg string, err error) {
	slog.Error(msg, "error", err.Error())
	os.Exit(1)
}

func dialBackendGRPC(addr string) (*grpc.ClientConn, error) {
	// admin-server 在广州跨区访问 Saudi 内网 CLB；gRPC 长连接如果长时间空闲，
	// TCP 层可能留下半开连接。客户端 keepalive 主动探活并触发重连，避免请求卡到 HTTP 超时。
	return grpc.Dial(addr,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithKeepaliveParams(keepalive.ClientParameters{
			Time:                30 * time.Second,
			Timeout:             10 * time.Second,
			PermitWithoutStream: true,
		}),
	)
}

func ensureDatabase(dsn string) error {
	parsed, err := mysqlDriver.ParseDSN(dsn)
	if err != nil {
		return err
	}
	if parsed.DBName == "" {
		return nil
	}

	dbName := parsed.DBName
	parsed.DBName = ""

	db, err := sql.Open("mysql", parsed.FormatDSN())
	if err != nil {
		return err
	}
	defer db.Close()

	escaped := strings.ReplaceAll(dbName, "`", "``")
	_, err = db.Exec("CREATE DATABASE IF NOT EXISTS `" + escaped + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
	return err
}
