package main

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

	"hfb_sys/backend/internal/config"
	"hfb_sys/backend/internal/database"
	"hfb_sys/backend/internal/jobs/ordertimeout"
	"hfb_sys/backend/internal/jobs/refundretry"
	"hfb_sys/backend/internal/logging"
	"hfb_sys/backend/internal/modules/adminauth"
	"hfb_sys/backend/internal/modules/payment"
	"hfb_sys/backend/internal/modules/paymentconfig"
	"hfb_sys/backend/internal/router"

	"go.uber.org/zap"
	"gorm.io/gorm"
)

// @title           HFB Sys API
// @version         1.0
// @description     哈夫币租号平台 API 文档
// @termsOfService  http://swagger.io/terms/

// @contact.name   API Support
// @contact.email  support@hfb.com

// @license.name  MIT
// @license.url   https://opensource.org/licenses/MIT

// @host      localhost:8080
// @BasePath  /api

// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.

func main() {
	cfg := config.Load()
	logger, err := logging.New(cfg.Log)
	if err != nil {
		panic(err)
	}
	defer func() {
		_ = logger.Sync()
	}()
	if err := cfg.ValidateProductionSecurity(); err != nil {
		logger.Fatal("production security config invalid", zap.Error(err))
	}

	var deps router.Dependencies
	db, err := database.OpenMySQL(cfg.MySQLDSN, cfg.Log.Level)
	if err != nil {
		logger.Warn("mysql unavailable; database-backed APIs will return 503", zap.Error(err))
	} else {
		deps.DB = db
		if err := adminauth.BootstrapAdmin(context.Background(), db, adminauth.BootstrapConfig{
			AppEnv:   cfg.AppEnv,
			Username: cfg.BootstrapAdminUsername,
			Password: cfg.BootstrapAdminPassword,
			Nickname: cfg.BootstrapAdminNickname,
		}); err != nil {
			logger.Fatal("admin bootstrap failed", zap.Error(err))
		}
	}
	redisClient, err := database.OpenRedis(context.Background(), database.RedisConfig{
		Addr:     cfg.RedisAddr,
		Password: cfg.RedisPassword,
		DB:       cfg.RedisDB,
	})
	if err != nil {
		logger.Warn("redis unavailable; redis-backed APIs will return 503", zap.Error(err))
	} else {
		deps.Redis = redisClient
		defer func() {
			_ = redisClient.Close()
		}()
	}

	engine := router.New(cfg, deps, logger)
	jobCtx, stopJobs := context.WithCancel(context.Background())
	defer stopJobs()
	if deps.DB != nil {
		ordertimeout.New(deps.DB, deps.Redis, logger).Start(jobCtx)
		if paymentConfigRepo := newPaymentConfigRepositoryForJobs(cfg, deps.DB, logger); paymentConfigRepo != nil {
			paymentRepo := payment.NewRepository(deps.DB, paymentConfigRepo, nil, payment.WithLogger(logger))
			refundretry.New(deps.DB, deps.Redis, logger, paymentRepo).Start(jobCtx)
		}
	}
	server := &http.Server{
		Addr:              cfg.AppAddr,
		Handler:           engine,
		ReadHeaderTimeout: 5 * time.Second,
	}

	go func() {
		logger.Info("api server starting", zap.String("addr", cfg.AppAddr))
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			logger.Fatal("api server failed", zap.Error(err))
		}
	}()

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

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := server.Shutdown(ctx); err != nil {
		logger.Fatal("api server shutdown failed", zap.Error(err))
	}
	logger.Info("api server stopped")
}

func newPaymentConfigRepositoryForJobs(cfg config.Config, db *gorm.DB, logger *zap.Logger) *paymentconfig.Repository {
	encryptionKey := cfg.PaymentConfigEncryptionKey
	var encryptor paymentconfig.Encryptor
	if encryptionKey != "" {
		if aesEncryptor, err := paymentconfig.NewAESEncryptor(encryptionKey); err == nil {
			encryptor = aesEncryptor
		}
	}
	if encryptor == nil {
		if config.IsProductionEnv(cfg.AppEnv) {
			logger.Fatal("PAYMENT_CONFIG_ENCRYPTION_KEY not set or invalid")
		}
		encryptor = &paymentconfig.MockEncryptor{}
		logger.Warn("PAYMENT_CONFIG_ENCRYPTION_KEY not set or invalid, using MockEncryptor for jobs")
	}
	return paymentconfig.NewRepository(db, encryptor)
}
