package db

import (
	"database/sql"
	"fmt"
	"reflect"
	"server/MergeConst"
	"server/conf"
	GoUtil "server/game_util"
	"server/pkg/github.com/name5566/leaf/log"
	"strings"
	"sync"
	"time"

	// "server/game"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var SqlDb *sqlx.DB
var sqlDbMu sync.RWMutex

// GetDB 线程安全地获取数据库连接
func GetDB() *sqlx.DB {
	sqlDbMu.RLock()
	defer sqlDbMu.RUnlock()
	return SqlDb
}

// GetDBOrPanic 获取数据库连接，如果为 nil 则记录错误
func GetDBOrPanic() *sqlx.DB {
	db := GetDB()
	if db == nil {
		log.Error("Database connection is nil, please check database initialization")
	}
	return db
}

// EnsureDB 确保数据库连接可用，如果不可用则返回错误
func EnsureDB() (*sqlx.DB, error) {
	db := GetDB()
	if db == nil {
		return nil, fmt.Errorf("database connection is nil")
	}
	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("database ping failed: %w", err)
	}
	return db, nil
}

// 封装创建连接
func connectMySQL() (*sqlx.DB, error) {
	MysqlPwd, _ := GoUtil.Decrypt(conf.Server.MySqlPwd)
	// 减少超时时间，避免长时间阻塞
	connect := fmt.Sprintf("%s:%s@(%s:%s)/%s?timeout=10s&readTimeout=15s&writeTimeout=15s&parseTime=true", conf.Server.MySqlUsr, MysqlPwd, conf.Server.MySqlAddr, conf.Server.MySqlPort, conf.Server.DbName)
	db, err := sqlx.Connect("mysql", connect)
	if err != nil {
		return nil, err
	}
	// 增加最大连接数，减少连接等待时间
	db.SetMaxOpenConns(100)
	db.SetMaxIdleConns(20)
	db.SetConnMaxLifetime(30 * time.Minute) // 减少连接生命周期
	db.SetConnMaxIdleTime(5 * time.Minute)  // 减少空闲时间
	return db, nil
}

func InitDB() {
	//"用户名:密码@[连接方式](主机名:端口号)/数据库名"
	db, err := connectMySQL()
	if err != nil {
		log.Debug("connect mysql failed: %v", err)
		return
	}
	sqlDbMu.Lock()
	SqlDb = db
	sqlDbMu.Unlock()
	log.Debug("connect mysql success")

	// 定时检测与重连
	go func() {
		ticker := time.NewTicker(5 * time.Second) // 改为5秒检测一次，降低频率
		defer ticker.Stop()
		for range ticker.C {
			sqlDbMu.RLock()
			cur := SqlDb
			sqlDbMu.RUnlock()

			if cur == nil {
				log.Debug("mysql connection is nil, start reconnect")
				ReconnectDB()
				continue
			}

			// Ping 操作不持有锁，避免阻塞其他操作
			if err := cur.Ping(); err != nil {
				log.Debug("mysql ping failed: %v, start reconnect", err)
				ReconnectDB()
			}
		}
	}()
}

// 自动重连
func ReconnectDB() {
	sqlDbMu.Lock()
	defer sqlDbMu.Unlock()
	newDb, err := connectMySQL()
	if err != nil {
		log.Debug("mysql reconnect failed: %v", err)
		return
	}
	if SqlDb != nil {
		_ = SqlDb.Close()
	}
	SqlDb = newDb
	log.Debug("mysql reconnect success")
}

func SeriesTransaction(sqlstrs []string, params [][]any) (err error) {
	sqlDb := GetDB()
	if sqlDb == nil {
		return fmt.Errorf("database connection is nil")
	}

	tx, err := sqlDb.Begin()
	if err != nil {
		log.Debug("Transaction failed, err:%v\n", err)
		return err
	}

	defer func() {
		if err != nil {
			log.Debug("Transaction failed, err:%v\n", err)
			_ = tx.Rollback()
		} else {
			err = tx.Commit()
			return
		}
	}()

	for i := 0; i < len(sqlstrs); i++ {
		_, err := tx.Exec(sqlstrs[i], params[i]...)
		if err != nil {
			log.Debug("Transaction failed, err:%v\n", err)
			return err
		}

	}

	return
}

// "UPDATE user SET age = ?, degree = ? WHERE id = ?"
func FormatAllMemUpdateDb(u interface{}, tableName string, Exclude string) (err error) {
	t := reflect.TypeOf(u)
	len1 := t.Elem().NumField()
	Fields := make([]string, len1)
	Values := make([]interface{}, len1)
	pp := reflect.ValueOf(u)
	origin := "UPDATE " + tableName + " SET "
	index := 0
	var keyValue interface{}
	for i := 0; i < len1; i++ {
		field := t.Elem().Field(i)
		if field.Tag.Get("db") != Exclude {
			Fields[index] = "`" + field.Tag.Get("db") + "` = ?"

			ufield := pp.Elem().FieldByName(field.Name)
			k := ufield.Kind()
			if k == reflect.String {
				Values[index] = ufield.String()
			}
			if k == reflect.Int32 || k == reflect.Int || k == reflect.Int64 {
				Values[index] = ufield.Int()
			}
			index++
		} else {
			ufield := pp.Elem().FieldByName(field.Name)
			k := ufield.Kind()
			if k == reflect.String {
				keyValue = ufield.String()
			}
			if k == reflect.Int32 || k == reflect.Int || k == reflect.Int64 {
				keyValue = ufield.Int()
			}
		}
	}
	Values[index] = keyValue
	// Values = append(Values, keyValue)
	origin += strings.Join(Fields, ",")
	strLen := len(origin)
	origin = origin[:strLen-1]
	origin = origin + " WHERE `" + Exclude + "` = ?"
	sqlStr := origin

	_, err = SqlDb.Exec(sqlStr, Values...)
	if err != nil {
		log.Debug("table: %s, sql :%s, exec failed, err:%v\n", tableName, sqlStr, err)
		return
	}
	return
}
func FormatAllMemInsertDb(u interface{}, tableName string) (insertID int64, err error) {
	t := reflect.TypeOf(u)
	len := t.Elem().NumField()
	Fields := make([]string, len)
	Fields1 := make([]string, len)
	FieldNames := make([]string, len)
	Values := make([]interface{}, len)
	pp := reflect.ValueOf(u)
	origin := "INSERT INTO " + tableName + "("
	for i := 0; i < len; i++ {
		field := t.Elem().Field(i)
		Fields[i] = field.Tag.Get("db")
		Fields1[i] = "?"
		FieldNames[i] = field.Name
		ufield := pp.Elem().FieldByName(field.Name)
		k := ufield.Kind()
		if k == reflect.String {
			Values[i] = ufield.String()
		}
		if k == reflect.Int32 {
			Values[i] = ufield.Int()
		}
		if k == reflect.Int64 {
			Values[i] = ufield.Int()
		}
	}
	for i := range Fields {
		Fields[i] = "`" + Fields[i] + "`"
	}
	origin += strings.Join(Fields, ",")
	origin += ") VALUES("
	origin += strings.Join(Fields1, ",")
	origin += ")"
	sqlStr := origin
	result, err := SqlDb.Exec(sqlStr, Values...)
	if err != nil {
		log.Debug("table: %s, sql :%s, exec failed, err:%v\n", tableName, sqlStr, err)
		return
	}
	insertID, err = result.LastInsertId()
	if err != nil {
		log.Debug("table: %s, sql :%s, exec failed, err:%v\n", tableName, sqlStr, err)
		return
	}
	return
}

func GetPlayerBaseInfoFromDbByName(name string) *ResPlayerBaseInfo {
	sqlStr := "SELECT * FROM t_player_baseinfo WHERE user_name = ?"
	var res ResPlayerBaseInfo
	if err := SqlDb.Get(&res, sqlStr, name); err != nil {
		log.Debug("table: %s, sql :%s, exec failed, err:%v\n", "PlayerBaseInfo", sqlStr, err)
		return nil
	}
	return &res
}

func GetPlayerBan(name string) int64 {
	sqlStr := "SELECT ban FROM t_player_baseinfo WHERE user_name = ?"
	var ban int64
	if err := SqlDb.Get(&ban, sqlStr, name); err != nil {
		log.Debug("table: %s, sql :%s, exec failed, err:%v\n", "PlayerBaseInfo", sqlStr, err)
		return 0
	}
	return ban
}

func UpdatePlayerBan(uid int64, ban int64) error {
	sqlStr := "UPDATE t_player_baseinfo SET ban = ? WHERE dwUin = ?"
	_, err := SqlDb.Exec(sqlStr, ban, uid)
	return err
}

func UpdatePlayerBaseInfoName(oldName, newName string) error {
	sqlStr := "UPDATE t_player_baseinfo SET user_name = ? WHERE user_name = ?"
	_, err := SqlDb.Exec(sqlStr, newName, oldName)
	return err
}

func GetPlayerBaseInfoFromDbById(id int32) *ResPlayerBaseInfo {
	sqlStr := "SELECT * FROM t_player_baseinfo WHERE dwUin = ?"
	var res ResPlayerBaseInfo
	if err := SqlDb.Get(&res, sqlStr, id); err != nil {
		log.Debug("table: %s, sql :%s, exec failed, err:%v\n", "PlayerBaseInfo", sqlStr, err)
		return nil
	}
	return &res
}

func GetAccountInfoFromDb(name string) *Db_Account {
	sqlStr := "SELECT * FROM t_account WHERE user_name = ?"
	var res Db_Account
	if err := SqlDb.Get(&res, sqlStr, name); err != nil {
		log.Debug("登录的账号不存在：%s", name)
		return nil
	}
	return &res
}

func ResetAccountData(oldName, newName string) error {
	sqlStr := "UPDATE t_account SET user_name = ? WHERE user_name = ?"
	_, err := SqlDb.Exec(sqlStr, newName, oldName)
	if err != nil {
		return err
	}
	sqlStr = "UPDATE t_player_baseinfo SET user_name = ? WHERE user_name = ?"
	_, err = SqlDb.Exec(sqlStr, newName, oldName)
	return err
}

func UpdateAccountInfoToDb(account *Db_Account) (err error) {
	_, err = SqlDb.Exec("UPDATE t_account SET user_password = ? WHERE user_name = ?", account.UserPassword, account.UserName)
	return
}

func UpdateAccountInfoName(account *Db_Account, newName string) (err error) {
	_, err = SqlDb.Exec("UPDATE t_account SET user_name = ? WHERE user_name = ?", newName, account.UserName)
	return
}

func UpdateAccountInfoDeviceToDb(account *Db_Account) (err error) {
	_, err = SqlDb.Exec("UPDATE t_account SET device_id = ? WHERE user_name = ?", account.DeviceId, account.UserName)
	return
}

func GetServerData(d interface{}, Key string) (err error) {
	sqlDb := GetDB()
	if sqlDb == nil {
		return fmt.Errorf("database connection is nil")
	}
	sql := "select * from t_server_mod where `key` = ?"
	err = sqlDb.Get(d, sql, Key)
	return
}

func SaveServerData(data *SqlServerModStruct) error {
	sqlDb := GetDB()
	if sqlDb == nil {
		return fmt.Errorf("database connection is nil")
	}
	sql := "update t_server_mod set `mData` = ? , `updateTime` = ? where `key` = ?"
	_, err := sqlDb.Exec(sql, data.ModData, data.UpdataTime, data.Key)
	return err
}

func SaveServerDataWithTx(tx *sql.Tx, data *SqlServerModStruct) error {
	sql := "update t_server_mod set `mData` = ? , `updateTime` = ? where `key` = ?"
	_, err := tx.Exec(sql, data.ModData, data.UpdataTime, data.Key)
	return err
}

func InsertServerData(data *SqlServerModStruct) error {
	sqlDb := GetDB()
	if sqlDb == nil {
		return fmt.Errorf("database connection is nil")
	}
	sql := "insert into t_server_mod (`mData` , `updateTime` ,`key`) Values (?,?,?)"
	_, err := sqlDb.Exec(sql, data.ModData, data.UpdataTime, data.Key)
	return err
}

func SavePlayerModData(data *SqlModStruct) error {
	sql := "INSERT INTO `t_player_mod` (`mData` , `updateTime` ,`dwUin`) Values (?,?,?) ON DUPLICATE KEY UPDATE `mData` = ? , `updateTime` = ? "
	_, err := SqlDb.Exec(sql, data.ModData, data.UpdataTime, data.DwUin, data.ModData, data.UpdataTime)
	return err
}

func InsertPlayerModData(data *SqlModStruct) error {
	sql := "insert into t_player_mod (`mData` , `updateTime` ,`dwUin`) Values (?,?,?)"
	_, err := SqlDb.Exec(sql, data.ModData, data.UpdataTime, data.DwUin)
	return err
}

func SavePlayerClientData(data *SqlModStruct) error {
	sql := "INSERT INTO `t_player_client_data` (`mData` , `updateTime` ,`dwUin`) Values (?,?,?) ON DUPLICATE KEY UPDATE `mData` = ? , `updateTime` = ? "
	_, err := SqlDb.Exec(sql, data.ModData, data.UpdataTime, data.DwUin, data.ModData, data.UpdataTime)
	return err
}

func GetPlayerClientData(d interface{}, Key string) (err error) {
	sql := "select * from t_server_mod where `dwUin` = ?"
	err = SqlDb.Get(d, sql, Key)
	return
}

func GetServerMailData(data *[]*SqlServerMailStruct) error {
	sql := "select * from system_mail_info"
	err := SqlDb.Select(data, sql)
	return err
}

func CreateOrderSn(Uid, ChargeId int, OrderSn, Platform, Channel string, Price float64, Currency, Extra string) error {
	sql := "insert into t_player_charge (`Uid`,`OrderId`, `ProductId`, `Price`,`Currency`, `CreateTime`, `PayPlatform`, `PayChannel`, `PayChannelExtra`) Values (?,?,?,?,?,?,?,?,?)"
	Now := GoUtil.Now()
	_, err := SqlDb.Exec(sql, Uid, OrderSn, ChargeId, Price, Currency, Now, Platform, Channel, Extra)
	return err
}

func GetPlayerChargeData(OrderSn string) (*SqlChargeOrderStruct, error) {
	sql := "select * from t_player_charge where OrderId = ?"
	data := &SqlChargeOrderStruct{}
	err := SqlDb.Get(data, sql, OrderSn)
	return data, err
}

func GetPlayerChargeDataList(Uid int) ([]*SqlChargeOrderStruct, error) {
	sql := "select * from t_player_charge where Uid = ? and PayStatus = ?"
	data := &[]*SqlChargeOrderStruct{}
	err := SqlDb.Select(data, sql, Uid, MergeConst.ORDER_STATUS_PAY)
	return *data, err
}

func GetPlayerPayChannelOrderId(OrderSn string) (*SqlChargeOrderStruct, error) {
	sql := "select * from t_player_charge where PayChannelOrderId = ?"
	data := &SqlChargeOrderStruct{}
	err := SqlDb.Get(data, sql, OrderSn)
	return data, err
}

func UpdatePlayerChargeData(data *SqlChargeOrderStruct) error {
	sql := "update t_player_charge set PayTime = ?, PayStatus = ?, PayChannelOrderId = ? where OrderId = ?"
	_, err := SqlDb.Exec(sql, data.PayTime, data.PayStatus, data.PayChannelOrderId, data.OrderId)
	return err
}

func SearchPlayer(key string) ([]*ResPlayerBaseInfo, error) {
	sql := "select * from t_player_baseinfo where nick_name like ? limit 10"
	data := &[]*ResPlayerBaseInfo{}
	err := SqlDb.Select(data, sql, "%"+key+"%")
	return *data, err
}

func GetCommendPlayerFromDb(uid, login int64, level int) ([]int, error) {
	sqlStr := "SELECT dwUin FROM t_player_baseinfo WHERE dwUin != ? AND logout_time > ? AND level >= ? ORDER BY logout_time DESC LIMIT 1000"
	var res []int
	if err := SqlDb.Select(&res, sqlStr, uid, login, level); err != nil {
		log.Debug("table: %s, sql :%s, exec failed, err:%v\n", "PlayerBaseInfo", sqlStr, err)
		return nil, err
	}
	return res, nil
}
