package handlers

import (
	"Wine-Server/utils"
	"Wine-Server/utils/tables"
	"context"
	"database/sql"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
	"github.com/wechatpay-apiv3/wechatpay-go/core"
	"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
	"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
	"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
	"github.com/wechatpay-apiv3/wechatpay-go/core/option"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
	wx "github.com/wechatpay-apiv3/wechatpay-go/utils"
	"log"
	"os"
)

type routeFn func(*gin.Engine)
type createTableFn func() error

type App struct {
	router  *gin.Engine
	address string
}

func lostHandler(ctx *gin.Context) {
	ctx.JSON(utils.HttpNotFound, utils.Fail("api/resource not found"))
}

func loggerFormat(timeFmt string) gin.HandlerFunc {
	return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
		return utils.Format("[wine] %s - %s [%s %s %s] %s, %s %d %s: %s\n",
			param.ClientIP,
			param.TimeStamp.Format(timeFmt),
			param.MethodColor(), param.Method, param.ResetColor(),
			param.Path,
			param.StatusCodeColor(), param.StatusCode, param.ResetColor(),
			param.ErrorMessage,
		)
	})
}

func CreateApp(config *utils.Config) *App {
	if config.Release {
		gin.SetMode(gin.ReleaseMode)
	} else {
		gin.SetMode(gin.DebugMode)
	}

	utils.ServerPrefix = config.ServerPrefix
	utils.Logger = log.New(os.Stderr, "[wine] ", log.Ldate|log.Ltime|log.Lshortfile|log.Lmsgprefix)

	initWxPay(config)
	initDatabase(config)
	router := createRouter(config)

	return &App{router: router, address: config.ServerAddr}
}

func (app *App) RouteRegister(routeFns ...routeFn) {
	for _, fn := range routeFns {
		fn(app.router)
	}
}

func (app *App) Start() {
	err := app.router.Run(app.address)
	if err != nil {
		utils.Logger.Printf("server can't run on address[%s] for: %s", app.address, err)
	}
}

func initWxPay(config *utils.Config) {
	utils.WxTitle, utils.WxV3Key = config.WxPayTitle, config.WxApiV3Key
	utils.WxAppId, utils.WxMchId = config.WxAppId, config.WxMerchantAcc
	utils.WxCertSeq, utils.WxCertPath = config.WxApiCertSeq, config.WxApiCertPath
	var err error
	utils.WxPrivateKey, err = wx.LoadPrivateKeyWithPath(config.WxApiCertPath)
	if err != nil {
		utils.Logger.Fatalf("load merchant private key error: %s\n", err)
	}
	opts := []core.ClientOption{
		option.WithWechatPayAutoAuthCipher(config.WxMerchantAcc, config.WxApiCertSeq, utils.WxPrivateKey, config.WxApiV3Key),
	}
	utils.WxPayCli = context.Background()
	client, err := core.NewClient(utils.WxPayCli, opts...)
	if err != nil {
		utils.Logger.Fatalf("new wechat pay client error: %s\n", err)
	}
	utils.WxPaySrv = native.NativeApiService{Client: client}

	err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(
		utils.WxPayCli, utils.WxPrivateKey, config.WxApiCertSeq, config.WxMerchantAcc, config.WxApiV3Key,
	)
	visitor := downloader.MgrInstance().GetCertificateVisitor(config.WxMerchantAcc)
	utils.WxCrtHdr, err = notify.NewRSANotifyHandler(config.WxApiV3Key, verifiers.NewSHA256WithRSAVerifier(visitor))
	if err != nil {
		utils.Logger.Fatalf("wxpay notice handler init error: %s\n", err)
	}
}

func initDatabase(config *utils.Config) {
	// init
	utils.Redis = redis.NewClient(&redis.Options{
		Addr:     utils.Format("%s:%d", config.RedisHost, config.RedisPort),
		Password: config.RedisPass, DB: config.RedisDatabase,
	})
	db, err := sql.Open(
		"mysql",
		utils.Format(
			"%s:%s@tcp(%s:%d)/%s?loc=Local&charset=utf8&parseTime=true",
			config.MysqlUser, config.MysqlPass, config.MysqlHost, config.MysqlPort, config.MysqlDatabase,
		),
	)
	if err != nil {
		utils.Logger.Fatalf("mysql init failed: %s\n", err)
	}
	err = db.Ping()
	if err != nil {
		utils.Logger.Fatalf("mysql connection failed: %s\n", err)
	}
	utils.Mysql = db

	// create
	toCreate := []createTableFn{
		tables.CreateAdvertiseTable, tables.CreateDeviceTable, tables.CreateManagerTable,
		tables.CreateParamsTable, tables.CreateVersionTable, tables.CreateWineTable,
		tables.CreateTradeTable, tables.CreateWorkerTable, tables.CreateUserTable,
		tables.CreateRefundTable, tables.CreateOperationTable,
	}
	for _, Fn := range toCreate {
		err = Fn()
		if err != nil {
			utils.Logger.Fatalf("create mysql tables failed: %s\n", err)
		}
	}
}

func createRouter(config *utils.Config) *gin.Engine {
	router := gin.New()
	err := router.SetTrustedProxies([]string{"127.0.0.1"})
	if err != nil {
		utils.Logger.Fatalf("can't trust '127.0.0.1', for: %s", err)
	}
	if err = utils.LoadRsaKeyPairs(config); err != nil {
		utils.Logger.Fatalf("can't generate private and public key.")
	}

	conf := cors.DefaultConfig()
	allow := append(conf.AllowHeaders, "Token", "Device")
	conf.AllowAllOrigins, conf.AllowHeaders = true, allow
	router.Use(loggerFormat(config.TimeFormat), gin.Recovery(), utils.ErrorHandler, cors.New(conf))
	router.NoRoute(lostHandler)
	router.Static("/static", "./static")

	return router
}
