package user

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/perfect-panel/server/internal/model/order"
	"github.com/perfect-panel/server/internal/model/subscribe"
	"github.com/perfect-panel/server/pkg/tool"

	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
)

const (
	cacheUserSubscribeTokenPrefix = "cache:user:subscribe:token:"
	cacheUserSubscribeUserPrefix  = "cache:user:subscribe:user:"
	cacheUserSubscribeIdPrefix    = "cache:user:subscribe:id:"
	cacheUserDeviceNumberPrefix   = "cache:user:device:number:"
	cacheUserDeviceIdPrefix       = "cache:user:device:id:"
)

type SubscribeDetails struct {
	Id          int64                `gorm:"primarykey"`
	UserId      int64                `gorm:"index:idx_user_id;not null;comment:User ID"`
	User        *User                `gorm:"foreignKey:UserId;references:Id"`
	OrderId     int64                `gorm:"index:idx_order_id;not null;comment:Order ID"`
	SubscribeId int64                `gorm:"index:idx_subscribe_id;not null;comment:Subscription ID"`
	Subscribe   *subscribe.Subscribe `gorm:"foreignKey:SubscribeId;references:Id"`
	NodeGroupId int64                `gorm:"index:idx_node_group_id;not null;default:0;comment:Node Group ID (single ID)"`
	StartTime   time.Time            `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"`
	ExpireTime  time.Time            `gorm:"default:NULL;comment:Subscription Expire Time"`
	FinishedAt  *time.Time           `gorm:"default:NULL;comment:Finished Time"`
	Traffic     int64                `gorm:"default:0;comment:Traffic"`
	Download    int64                `gorm:"default:0;comment:Download Traffic"`
	Upload      int64                `gorm:"default:0;comment:Upload Traffic"`
	Token       string               `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
	UUID        string               `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
	Status      uint8                `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired; 4: Cancelled"`
	Note        string               `gorm:"type:varchar(500);default:'';comment:User note for subscription"`
	CreatedAt   time.Time            `gorm:"<-:create;comment:Creation Time"`
	UpdatedAt   time.Time            `gorm:"comment:Update Time"`
}

type SubscribeLogFilterParams struct {
	IP              string
	UserAgent       string
	UserId          int64
	Token           string
	UserSubscribeId int64
}

type LoginLogFilterParams struct {
	IP        string
	UserId    int64
	UserAgent string
	Success   *bool
}

type UserFilterParams struct {
	Search            string
	UserId            *int64
	SubscribeId       *int64
	UserSubscribeId   *int64
	ShortCode         string
	DeviceId          *int64
	FamilyJoined      *bool
	FamilyStatus      string
	FamilyOwnerUserId *int64
	FamilyId          *int64
	Order             string // Order by id, e.g., "desc"
	Unscoped          bool   // Whether to include soft-deleted records
}

type customUserLogicModel interface {
	QueryPageList(ctx context.Context, page, size int, filter *UserFilterParams) ([]*User, int64, error)
	FindOneByReferCode(ctx context.Context, referCode string) (*User, error)
	BatchDeleteUser(ctx context.Context, ids []int64, tx ...*gorm.DB) error
	InsertSubscribe(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error
	FindOneSubscribeByToken(ctx context.Context, token string) (*Subscribe, error)
	FindSingleModeAnchorSubscribe(ctx context.Context, userId int64) (*Subscribe, error)
	FindOneSubscribeByOrderId(ctx context.Context, orderId int64) (*Subscribe, error)
	FindOneSubscribe(ctx context.Context, id int64) (*Subscribe, error)
	UpdateSubscribe(ctx context.Context, data *Subscribe, tx ...*gorm.DB) error
	DeleteSubscribe(ctx context.Context, token string, tx ...*gorm.DB) error
	DeleteSubscribeById(ctx context.Context, id int64, tx ...*gorm.DB) error
	QueryUserSubscribe(ctx context.Context, userId int64, status ...int64) ([]*SubscribeDetails, error)
	FindOneSubscribeDetailsById(ctx context.Context, id int64) (*SubscribeDetails, error)
	FindOneUserSubscribe(ctx context.Context, id int64) (*SubscribeDetails, error)
	FindUsersSubscribeBySubscribeId(ctx context.Context, subscribeId int64) ([]*Subscribe, error)
	UpdateUserSubscribeWithTraffic(ctx context.Context, id, download, upload int64, isExpired bool, tx ...*gorm.DB) error
	QueryResisterUserTotalByDate(ctx context.Context, date time.Time) (int64, error)
	QueryResisterUserTotalByMonthly(ctx context.Context, date time.Time) (int64, error)
	QueryResisterUserTotal(ctx context.Context) (int64, error)
	QueryAdminUsers(ctx context.Context) ([]*User, error)
	UpdateUserCache(ctx context.Context, data *User) error
	UpdateUserSubscribeCache(ctx context.Context, data *Subscribe) error
	QueryActiveSubscriptions(ctx context.Context, subscribeId ...int64) (map[int64]int64, error)
	FindUserAuthMethods(ctx context.Context, userId int64) ([]*AuthMethods, error)
	InsertUserAuthMethods(ctx context.Context, data *AuthMethods, tx ...*gorm.DB) error
	UpdateUserAuthMethods(ctx context.Context, data *AuthMethods, tx ...*gorm.DB) error
	DeleteUserAuthMethods(ctx context.Context, userId int64, platform string, tx ...*gorm.DB) error
	FindUserAuthMethodByOpenID(ctx context.Context, method, openID string) (*AuthMethods, error)
	FindUserAuthMethodByUserId(ctx context.Context, method string, userId int64) (*AuthMethods, error)
	FindUserAuthMethodByPlatform(ctx context.Context, userId int64, platform string) (*AuthMethods, error)
	FindOneByEmail(ctx context.Context, email string) (*User, error)
	FindOneDevice(ctx context.Context, id int64) (*Device, error)
	QueryDeviceList(ctx context.Context, userid int64) ([]*Device, int64, error)
	QueryDeviceListByUserIds(ctx context.Context, userIds []int64) ([]*Device, int64, error)
	QueryDevicePageList(ctx context.Context, userid, subscribeId int64, page, size int) ([]*Device, int64, error)
	UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
	FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
	DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error
	InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error

	ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error
	ClearUserCache(ctx context.Context, data ...*User) error
	ClearDeviceCache(ctx context.Context, devices ...*Device) error

	QueryDailyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error)
	QueryMonthlyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error)
	FindActiveSubscribe(ctx context.Context, userId int64) (*Subscribe, error)
	FindActiveSubscribesByUserIds(ctx context.Context, userIds []int64) (map[int64]*UserStatusInfo, error)
}

type UserStatusInfo struct {
	MemberStatus     string
	LastTrafficAt    *time.Time
	PurchasedPackage string
}

type UserStatisticsWithDate struct {
	Date              string
	Register          int64
	NewOrderUsers     int64
	RenewalOrderUsers int64
}

// NewModel returns a model for the database table.
func NewModel(conn *gorm.DB, c *redis.Client) Model {
	return &customUserModel{
		defaultUserModel: newUserModel(conn, c),
	}
}

// QueryPageList returns a list of records that meet the conditions.
func (m *customUserModel) QueryPageList(ctx context.Context, page, size int, filter *UserFilterParams) ([]*User, int64, error) {
	var list []*User
	var total int64
	err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
		joinedFamily := false
		joinFamily := func(c *gorm.DB) *gorm.DB {
			if joinedFamily {
				return c
			}
			joinedFamily = true
			return c.
				Joins("JOIN user_family_member ufm ON ufm.user_id = user.id AND ufm.deleted_at IS NULL AND ufm.status = ?", FamilyMemberActive).
				Joins("JOIN user_family uf ON uf.id = ufm.family_id AND uf.deleted_at IS NULL")
		}
		joinFamilyLeft := func(c *gorm.DB) *gorm.DB {
			if joinedFamily {
				return c
			}
			joinedFamily = true
			return c.
				Joins("LEFT JOIN user_family_member ufm ON ufm.user_id = user.id AND ufm.deleted_at IS NULL AND ufm.status = ?", FamilyMemberActive).
				Joins("LEFT JOIN user_family uf ON uf.id = ufm.family_id AND uf.deleted_at IS NULL")
		}

		if filter != nil {
			if filter.UserId != nil {
				conn = conn.Where("user.id =?", *filter.UserId)
			}
			if filter.Search != "" {
				// 尝试将搜索词解码为 device_no（8位hex → device id）
				if deviceId, err := tool.HashToDeviceId(filter.Search); err == nil && deviceId > 0 {
					conn = conn.Joins("LEFT JOIN user_auth_methods ON user.id = user_auth_methods.user_id").
						Joins("LEFT JOIN user_device ON user.id = user_device.user_id").
						Where("user_auth_methods.auth_identifier LIKE ? OR user.refer_code LIKE ? OR user_device.id = ?",
							"%"+filter.Search+"%", "%"+filter.Search+"%", deviceId)
				} else {
					conn = conn.Joins("LEFT JOIN user_auth_methods ON user.id = user_auth_methods.user_id").
						Where("user_auth_methods.auth_identifier LIKE ?", "%"+filter.Search+"%").Or("user.refer_code like ?", "%"+filter.Search+"%")
				}
			}
			if filter.UserSubscribeId != nil {
				conn = conn.Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id").
					Where("user_subscribe.id =? and `status` IN (0,1)", *filter.UserSubscribeId)
			}
			if filter.SubscribeId != nil {
				conn = conn.Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id").
					Where("user_subscribe.subscribe_id =? and `status` IN (0,1)", *filter.SubscribeId)
			}
			if filter.ShortCode != "" {
				conn = conn.Joins("LEFT JOIN user_device ON user.id = user_device.user_id").
					Where("user_device.short_code LIKE ?", "%"+filter.ShortCode+"%")
			}
			if filter.DeviceId != nil {
				conn = conn.Joins("LEFT JOIN user_device ud ON ud.user_id = user.id").
					Where("ud.id = ?", *filter.DeviceId)
			}

			if filter.FamilyJoined != nil {
				if *filter.FamilyJoined {
					conn = joinFamily(conn)
				} else {
					conn = joinFamilyLeft(conn)
					conn = conn.Where("ufm.id IS NULL")
				}
			}

			if filter.FamilyOwnerUserId != nil {
				conn = joinFamily(conn)
				conn = conn.Where("uf.owner_user_id = ?", *filter.FamilyOwnerUserId)
			}

			if filter.FamilyId != nil {
				conn = joinFamily(conn)
				conn = conn.Where("uf.id = ?", *filter.FamilyId)
			}

			if filter.FamilyStatus != "" {
				normalizedStatus := strings.ToLower(strings.TrimSpace(filter.FamilyStatus))
				var (
					statusValue uint8
					hasStatus   bool
				)
				switch normalizedStatus {
				case "active", "1":
					statusValue = FamilyStatusActive
					hasStatus = true
				case "disabled", "0":
					statusValue = 0
					hasStatus = true
				}
				if hasStatus {
					conn = joinFamily(conn)
					conn = conn.Where("uf.status = ?", statusValue)
				}
			}

			if filter.Order != "" {
				order := strings.ToUpper(filter.Order)
				if order != "ASC" && order != "DESC" {
					order = "DESC"
				}
				conn = conn.Order(fmt.Sprintf("user.id %s", order))
			} else {
				conn = conn.Order("user.id DESC")
			}
			if filter.Unscoped {
				conn = conn.Unscoped()
			}
		}
		return conn.Model(&User{}).Group("user.id").Count(&total).Limit(size).Offset((page-1)*size).Preload("UserDevices").Preload("AuthMethods", func(db *gorm.DB) *gorm.DB { return db.Order("user_auth_methods.auth_type desc") }).Find(&list).Error
	})
	return list, total, err
}

// BatchDeleteUser deletes multiple records by primary key.
func (m *customUserModel) BatchDeleteUser(ctx context.Context, ids []int64, tx ...*gorm.DB) error {
	var users []*User
	err := m.QueryNoCacheCtx(ctx, &users, func(conn *gorm.DB, v interface{}) error {
		if len(tx) > 0 {
			conn = tx[0]
		}
		return conn.Where("id in ?", ids).Preload("AuthMethods").Find(&users).Error
	})
	if err != nil {
		return err
	}
	return m.ExecCtx(ctx, func(conn *gorm.DB) error {
		return conn.Where("id in ?", ids).Delete(&User{}).Error
	}, m.batchGetCacheKeys(users...)...)
}

func (m *customUserModel) UpdateUserSubscribeWithTraffic(ctx context.Context, id, download, upload int64, isExpired bool, tx ...*gorm.DB) error {
	sub, err := m.FindOneSubscribe(ctx, id)
	if err != nil {
		return err
	}

	// 使用 defer 确保更新后清理缓存
	defer func() {
		if clearErr := m.ClearSubscribeCacheByModels(ctx, sub); clearErr != nil {
			// 记录清理缓存错误
		}
	}()

	return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error {
		if len(tx) > 0 {
			conn = tx[0]
		}

		// 根据订阅状态更新对应的流量字段
		if isExpired {
			// 过期期间,更新过期流量字段
			return conn.Model(&Subscribe{}).Where("id = ?", id).Updates(map[string]interface{}{
				"expired_download": gorm.Expr("expired_download + ?", download),
				"expired_upload":   gorm.Expr("expired_upload + ?", upload),
			}).Error
		} else {
			// 正常期间,更新正常流量字段
			return conn.Model(&Subscribe{}).Where("id = ?", id).Updates(map[string]interface{}{
				"download": gorm.Expr("download + ?", download),
				"upload":   gorm.Expr("upload + ?", upload),
			}).Error
		}
	})
}

func (m *customUserModel) QueryResisterUserTotalByDate(ctx context.Context, date time.Time) (int64, error) {
	var total int64
	start := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
	end := start.AddDate(0, 0, 1).Add(-time.Second)
	err := m.QueryNoCacheCtx(ctx, &total, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&User{}).Where("created_at > ? and created_at < ?", start, end).Count(&total).Error
	})
	return total, err
}

func (m *customUserModel) QueryResisterUserTotalByMonthly(ctx context.Context, date time.Time) (int64, error) {
	var total int64
	start := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, time.Local)
	end := start.AddDate(0, 1, 0).Add(-time.Nanosecond)
	err := m.QueryNoCacheCtx(ctx, &total, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&User{}).Where("created_at > ? and created_at < ?", start, end).Count(&total).Error
	})
	return total, err
}

func (m *customUserModel) QueryResisterUserTotal(ctx context.Context) (int64, error) {
	var total int64
	err := m.QueryNoCacheCtx(ctx, &total, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&User{}).Count(&total).Error
	})
	return total, err
}

func (m *customUserModel) QueryAdminUsers(ctx context.Context) ([]*User, error) {
	var data []*User
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&User{}).Preload("AuthMethods", func(db *gorm.DB) *gorm.DB { return db.Order("user_auth_methods.auth_type desc") }).Where("is_admin = ?", true).Find(&data).Error
	})
	return data, err
}

func (m *customUserModel) UpdateUserCache(ctx context.Context, data *User) error {
	return m.ClearUserCache(ctx, data)
}

func (m *customUserModel) FindOneByReferCode(ctx context.Context, referCode string) (*User, error) {
	var data User
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&User{}).Where("refer_code = ?", referCode).First(&data).Error
	})
	return &data, err
}

func (m *customUserModel) FindOneSubscribeDetailsById(ctx context.Context, id int64) (*SubscribeDetails, error) {
	var data SubscribeDetails
	err := m.QueryNoCacheCtx(ctx, &data, func(conn *gorm.DB, v interface{}) error {
		return conn.Model(&Subscribe{}).Preload("Subscribe").Preload("User").Where("id = ?", id).First(&data).Error
	})
	return &data, err
}

// QueryDailyUserStatisticsList Query daily user statistics list for the current month (from 1st to current date)
func (m *customUserModel) QueryDailyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error) {
	var results []UserStatisticsWithDate

	err := m.QueryNoCacheCtx(ctx, &results, func(conn *gorm.DB, v interface{}) error {
		firstDay := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, date.Location())

		// 子查询：统计每天的新用户订单数量
		newOrderSub := conn.Model(&order.Order{}).
			Select("DATE_FORMAT(created_at, '%Y-%m-%d') AS date, COUNT(DISTINCT user_id) AS new_order_users").
			Where("type = 1 AND created_at BETWEEN ? AND ? AND status IN ? AND method != ?", firstDay, date, []int64{2, 5}, "balance").
			Group("DATE_FORMAT(created_at, '%Y-%m-%d')")

		// 子查询：统计每天的续费订单数量
		renewalOrderSub := conn.Model(&order.Order{}).
			Select("DATE_FORMAT(created_at, '%Y-%m-%d') AS date, COUNT(DISTINCT user_id) AS renewal_order_users").
			Where("type = 2 AND created_at BETWEEN ? AND ? AND status IN ? AND method != ?", firstDay, date, []int64{2, 5}, "balance").
			Group("DATE_FORMAT(created_at, '%Y-%m-%d')")

		return conn.Model(&User{}).
			Select(`
                DATE_FORMAT(user.created_at, '%Y-%m-%d') AS date,
                COUNT(*) AS register,
                IFNULL(MAX(n.new_order_users), 0) AS new_order_users,
                IFNULL(MAX(r.renewal_order_users), 0) AS renewal_order_users
            `).
			Joins("LEFT JOIN (?) AS n ON DATE_FORMAT(user.created_at, '%Y-%m-%d') = n.date", newOrderSub).
			Joins("LEFT JOIN (?) AS r ON DATE_FORMAT(user.created_at, '%Y-%m-%d') = r.date", renewalOrderSub).
			Where("user.created_at BETWEEN ? AND ?", firstDay, date).
			Group("DATE_FORMAT(user.created_at, '%Y-%m-%d')").
			Order("date ASC").
			Scan(v).Error
	})

	return results, err
}

// QueryMonthlyUserStatisticsList Query monthly user statistics list for the past 6 months
func (m *customUserModel) QueryMonthlyUserStatisticsList(ctx context.Context, date time.Time) ([]UserStatisticsWithDate, error) {
	var results []UserStatisticsWithDate

	err := m.QueryNoCacheCtx(ctx, &results, func(conn *gorm.DB, v interface{}) error {
		// 获取 6 个月前的日期
		sixMonthsAgo := date.AddDate(0, -5, 0)

		// 子查询：每月新订单用户数量
		newOrderSub := conn.Model(&order.Order{}).
			Select("DATE_FORMAT(created_at, '%Y-%m') AS date, COUNT(DISTINCT user_id) AS new_order_users").
			Where("type = 1 AND created_at >= ? AND status IN ? AND method != ?", sixMonthsAgo, []int64{2, 5}, "balance").
			Group("DATE_FORMAT(created_at, '%Y-%m')")

		// 子查询：每月续费订单用户数量
		renewalOrderSub := conn.Model(&order.Order{}).
			Select("DATE_FORMAT(created_at, '%Y-%m') AS date, COUNT(DISTINCT user_id) AS renewal_order_users").
			Where("type = 2 AND created_at >= ? AND status IN ? AND method != ?", sixMonthsAgo, []int64{2, 5}, "balance").
			Group("DATE_FORMAT(created_at, '%Y-%m')")

		return conn.Model(&User{}).
			Select(`
				DATE_FORMAT(user.created_at, '%Y-%m') AS date,
				COUNT(*) AS register,
				IFNULL(MAX(n.new_order_users), 0) AS new_order_users,
				IFNULL(MAX(r.renewal_order_users), 0) AS renewal_order_users
			`).
			Joins("LEFT JOIN (?) AS n ON DATE_FORMAT(user.created_at, '%Y-%m') = n.date", newOrderSub).
			Joins("LEFT JOIN (?) AS r ON DATE_FORMAT(user.created_at, '%Y-%m') = r.date", renewalOrderSub).
			Where("user.created_at >= ?", sixMonthsAgo).
			Group("DATE_FORMAT(user.created_at, '%Y-%m')").
			Order("date ASC").
			Scan(v).Error
	})

	return results, err
}

// FindActiveSubscribe finds the active subscription for a user
func (m *customUserModel) FindActiveSubscribe(ctx context.Context, userId int64) (*Subscribe, error) {
	var subscribe Subscribe
	err := m.QueryNoCacheCtx(ctx, &subscribe, func(conn *gorm.DB, v interface{}) error {
		now := time.Now()
		return conn.Where("user_id = ? AND status IN (0, 1) AND (expire_time > ? OR expire_time = ?)", userId, now, time.UnixMilli(0)).
			Order("expire_time DESC").
			First(v).Error
	})
	if err != nil {
		return nil, err
	}
	return &subscribe, nil
}
