package user

import (
	"context"
	"encoding/json"

	logmodel "github.com/perfect-panel/server/internal/model/log"
	"github.com/perfect-panel/server/internal/model/user"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/constant"
	"github.com/perfect-panel/server/pkg/hash"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"
	"gorm.io/gorm"
)

const (
	inviteRecordRoleInviter = "inviter"
	inviteRecordRoleInvitee = "invitee"
)

type GetInviteRecordsLogic struct {
	logger.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

type inviteRecordLog struct {
	Id        int64  `gorm:"column:id"`
	ObjectId  int64  `gorm:"column:object_id"`
	Content   string `gorm:"column:content"`
	CreatedAt int64  `gorm:"column:created_at"`
}

type inviteGiftContent struct {
	OrderNo string `json:"order_no"`
	Amount  int64  `json:"amount"`
}

type parsedInviteRecordLog struct {
	log     inviteRecordLog
	content inviteGiftContent
}

type inviteOrderUser struct {
	OrderNo            string `gorm:"column:order_no"`
	UserId             int64  `gorm:"column:user_id"`
	SubscriptionUserId int64  `gorm:"column:subscription_user_id"`
	RefererId          int64  `gorm:"column:referer_id"`
}

// Get invite gift records
func NewGetInviteRecordsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetInviteRecordsLogic {
	return &GetInviteRecordsLogic{
		Logger: logger.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequest) (resp *types.GetInviteRecordsResponse, err error) {
	u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
	if !ok {
		l.Errorw("[GetInviteRecords] user not found in context")
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
	}

	normalizeInviteRecordsPagination(req)

	visibleUserIds, err := l.resolveInviteRecordVisibleUserIds(u.Id)
	if err != nil {
		l.Errorw("[GetInviteRecords] resolve visible users failed",
			logger.Field("error", err.Error()),
			logger.Field("user_id", u.Id))
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "resolve visible users failed: %v", err.Error())
	}

	query := l.svcCtx.DB.WithContext(l.ctx).
		Table("system_logs").
		Where("type = ? AND object_id IN ?", logmodel.TypeGift.Uint8(), visibleUserIds).
		Where("JSON_VALID(content) = 1").
		Where("JSON_UNQUOTE(JSON_EXTRACT(content, '$.remark')) = ?", "邀请赠送")
	if req.StartTime > 0 {
		query = query.Where("created_at >= FROM_UNIXTIME(?)", req.StartTime)
	}
	if req.EndTime > 0 {
		query = query.Where("created_at <= FROM_UNIXTIME(?)", req.EndTime)
	}

	var logs []inviteRecordLog
	if err = query.
		Select("id, object_id, content, CAST(UNIX_TIMESTAMP(created_at) * 1000 AS SIGNED) AS created_at").
		Order("created_at DESC, id DESC").
		Scan(&logs).Error; err != nil {
		l.Errorw("[GetInviteRecords] query logs failed",
			logger.Field("error", err.Error()),
			logger.Field("user_id", u.Id))
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query logs failed: %v", err.Error())
	}

	parsedLogs, orderNos := l.parseInviteRecordContents(logs)
	if len(logs) == 0 || len(parsedLogs) == 0 {
		return &types.GetInviteRecordsResponse{Total: 0, List: []types.InviteRecord{}}, nil
	}

	orders, err := l.queryInviteRecordOrders(orderNos)
	if err != nil {
		l.Errorw("[GetInviteRecords] query orders failed",
			logger.Field("error", err.Error()),
			logger.Field("user_id", u.Id))
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query orders failed: %v", err.Error())
	}

	visibleUserIdSet := make(map[int64]struct{}, len(visibleUserIds))
	for _, userId := range visibleUserIds {
		visibleUserIdSet[userId] = struct{}{}
	}

	allRecords := make([]types.InviteRecord, 0, len(parsedLogs))
	for _, parsed := range parsedLogs {
		content := parsed.content
		logItem := parsed.log
		orderInfo, hasOrder := orders[content.OrderNo]
		if !l.canViewInviteRecord(u.Id, logItem.ObjectId, visibleUserIdSet, hasOrder, orderInfo) {
			continue
		}

		record := types.InviteRecord{
			Role:      inviteRecordRoleInviter,
			GiftDays:  content.Amount,
			OrderNo:   content.OrderNo,
			CreatedAt: logItem.CreatedAt,
		}

		if hasOrder {
			peerId := orderInfo.UserId
			if orderInfo.UserId == u.Id {
				record.Role = inviteRecordRoleInvitee
				peerId = u.RefererId
			}
			if peerId > 0 {
				record.PeerHash = hash.InvitePeerHash(peerId)
			}
		}

		allRecords = append(allRecords, record)
	}

	total := int64(len(allRecords))
	list := paginateInviteRecords(allRecords, req.Page, req.Size)
	return &types.GetInviteRecordsResponse{
		Total: total,
		List:  list,
	}, nil
}

func (l *GetInviteRecordsLogic) resolveInviteRecordVisibleUserIds(currentUserId int64) ([]int64, error) {
	visibleUserIds := []int64{currentUserId}

	var relation struct {
		FamilyId    int64 `gorm:"column:family_id"`
		Role        uint8 `gorm:"column:role"`
		OwnerUserId int64 `gorm:"column:owner_user_id"`
	}
	err := l.svcCtx.DB.WithContext(l.ctx).
		Model(&user.UserFamilyMember{}).
		Select("user_family_member.family_id, user_family_member.role, user_family.owner_user_id").
		Joins("JOIN user_family ON user_family.id = user_family_member.family_id AND user_family.deleted_at IS NULL AND user_family.status = ?", user.FamilyStatusActive).
		Where("user_family_member.user_id = ? AND user_family_member.status = ? AND user_family_member.deleted_at IS NULL", currentUserId, user.FamilyMemberActive).
		First(&relation).Error
	if err == nil {
		if relation.Role != user.FamilyRoleOwner && relation.OwnerUserId > 0 && relation.OwnerUserId != currentUserId {
			visibleUserIds = append(visibleUserIds, relation.OwnerUserId)
		}
		return visibleUserIds, nil
	}
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return visibleUserIds, nil
	}
	return nil, err
}

func (l *GetInviteRecordsLogic) canViewInviteRecord(currentUserId, logObjectId int64, visibleUserIds map[int64]struct{}, hasOrder bool, orderInfo inviteOrderUser) bool {
	if _, ok := visibleUserIds[logObjectId]; !ok {
		return false
	}
	if logObjectId == currentUserId {
		if hasOrder {
			return orderInfo.UserId == currentUserId || orderInfo.RefererId == currentUserId
		}
		return true
	}
	return true
}

func normalizeInviteRecordsPagination(req *types.GetInviteRecordsRequest) {
	if req.Page < 1 {
		req.Page = 1
	}
	if req.Size < 1 {
		req.Size = 10
	}
	if req.Size > 100 {
		req.Size = 100
	}
}

func paginateInviteRecords(records []types.InviteRecord, page, size int) []types.InviteRecord {
	start := (page - 1) * size
	if start >= len(records) {
		return []types.InviteRecord{}
	}
	end := start + size
	if end > len(records) {
		end = len(records)
	}
	return records[start:end]
}

func (l *GetInviteRecordsLogic) parseInviteRecordContents(logs []inviteRecordLog) ([]parsedInviteRecordLog, []string) {
	parsedLogs := make([]parsedInviteRecordLog, 0, len(logs))
	orderNos := make([]string, 0, len(logs))
	for _, logItem := range logs {
		var content inviteGiftContent
		if err := json.Unmarshal([]byte(logItem.Content), &content); err != nil {
			l.Infow("[GetInviteRecords] parse content failed",
				logger.Field("error", err.Error()),
				logger.Field("log_id", logItem.Id))
			continue
		}
		parsedLogs = append(parsedLogs, parsedInviteRecordLog{log: logItem, content: content})
		if content.OrderNo != "" {
			orderNos = append(orderNos, content.OrderNo)
		}
	}
	return parsedLogs, orderNos
}

func (l *GetInviteRecordsLogic) queryInviteRecordOrders(orderNos []string) (map[string]inviteOrderUser, error) {
	orders := make(map[string]inviteOrderUser, len(orderNos))
	if len(orderNos) == 0 {
		return orders, nil
	}

	var orderData []inviteOrderUser
	err := l.svcCtx.DB.WithContext(l.ctx).
		Table("`order`").
		Select("`order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id").
		Joins("LEFT JOIN user invitee ON invitee.id = `order`.user_id").
		Where("`order`.order_no IN ?", orderNos).
		Scan(&orderData).Error
	if err != nil {
		return nil, err
	}

	for _, item := range orderData {
		orders[item.OrderNo] = item
	}
	return orders, nil
}
