package usecase

import (
	"backend/pkg/chat/domain/entity"
	"backend/pkg/chat/domain/repository"
	"backend/pkg/chat/domain/usecase"
	"backend/pkg/library/centrifugo"
	errs "backend/pkg/library/errors"
	"backend/pkg/utils"
	"context"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"math"
	"time"

	"github.com/gocql/gocql"
	"github.com/google/uuid"
)

const (
	defaultPageSize = 20
	maxPageSize     = 100 // 設置最大分頁大小
)

type MessageUseCaseParam struct {
	MessageRepo repository.MessageRepository
	RoomRepo    repository.RoomRepository
	MsgClient   *centrifugo.Client
	Logger      errs.Logger
}

type MessageUseCase struct {
	MessageUseCaseParam
}

// NewMessageUseCase 創建新的訊息 UseCase
func NewMessageUseCase(param MessageUseCaseParam) usecase.MessageUseCase {
	return &MessageUseCase{
		param,
	}
}

func (use *MessageUseCase) SendMessage(ctx context.Context, param usecase.SendMessageReq) error {
	// 驗證輸入參數
	if err := use.validateSendMessageReq(param); err != nil {
		return err
	}

	// 驗證使用者是否在房間中（優先檢查，避免不必要的去重操作）
	if err := use.verifyRoomMembership(ctx, param.UID, param.RoomID); err != nil {
		return err
	}

	// 去重檢查
	now := time.Now().UTC()
	bucketSec := now.Unix()
	contentMD5 := calculateMD5(param.Content)

	isDuplicate, err := use.MessageRepo.CheckAndInsertDedup(ctx, repository.CheckDupReq{
		RoomID:     param.RoomID,
		UID:        param.UID, // 修復：應該是 param.UID 而不是 param.RoomID
		BucketSec:  bucketSec,
		ContentMD5: contentMD5,
	})
	if err != nil {
		return use.logError("messageRepo.CheckAndInsertDedup", param, err, "failed to check message deduplication")
	}

	if isDuplicate {
		return errs.InputInvalidFormatError("duplicate message detected")
	}

	// 建立並儲存訊息
	msg, err := use.createMessage(param, now)
	if err != nil {
		return err
	}

	if err = use.MessageRepo.Insert(ctx, msg); err != nil {
		return use.logError("messageRepo.Insert", param, err, "failed to insert message")
	}

	// 發布到 Centrifugo（非阻塞，失敗不影響主流程）
	use.publishToCentrifugo(ctx, param.RoomID, msg, now)

	return nil
}

// validateSendMessageReq 驗證發送訊息的請求參數
func (use *MessageUseCase) validateSendMessageReq(param usecase.SendMessageReq) error {
	if param.Content == "" {
		return errs.InputInvalidFormatError("content cannot be empty")
	}
	if param.RoomID == "" {
		return errs.InputInvalidFormatError("room_id cannot be empty")
	}
	if param.UID == "" {
		return errs.InputInvalidFormatError("uid cannot be empty")
	}
	return nil
}

// verifyRoomMembership 驗證使用者是否在房間中
func (use *MessageUseCase) verifyRoomMembership(ctx context.Context, uid, roomID string) error {
	isMember, err := use.RoomRepo.IsUserInRoom(ctx, uid, roomID)
	if err != nil {
		return use.logError("roomRepo.IsUserInRoom", map[string]interface{}{
			"uid":    uid,
			"roomID": roomID,
		}, err, "failed to check room membership")
	}

	if !isMember {
		return errs.AuthForbiddenErrorL(
			use.Logger,
			[]errs.LogField{
				{Key: "uid", Val: uid},
				{Key: "roomID", Val: roomID},
			},
			"user is not a member of the room")
	}

	return nil
}

// createMessage 建立訊息實體
func (use *MessageUseCase) createMessage(param usecase.SendMessageReq, now time.Time) (*entity.Message, error) {
	roomID, err := gocql.ParseUUID(param.RoomID)
	if err != nil {
		return nil, errs.InputInvalidFormatError("invalid room_id format").Wrap(err)
	}

	msgID, err := uuid.NewV7()
	if err != nil {
		return nil, errs.SysInternalError("failed to generate message id").Wrap(err)
	}

	return &entity.Message{
		RoomID:    roomID,
		BucketDay: utils.GetBucketDay(now),
		UID:       param.UID,
		MsgID:     msgID,
		Content:   param.Content,
	}, nil
}

// publishToCentrifugo 發布訊息到 Centrifugo
func (use *MessageUseCase) publishToCentrifugo(ctx context.Context, roomID string, msg *entity.Message, now time.Time) {
	channel := fmt.Sprintf("room:%s", roomID)
	messageData := map[string]interface{}{
		"msg_id":    msg.MsgID.String(),
		"uid":       msg.UID,
		"content":   msg.Content,
		"timestamp": now.UnixNano(), // 使用實際時間戳，而不是 msg.TS（可能為 0）
		"room_id":   msg.RoomID.String(),
	}

	if _, err := use.MsgClient.PublishJSON(ctx, channel, messageData); err != nil {
		// 記錄錯誤但不影響主流程，因為訊息已經成功儲存
		if use.Logger != nil {
			use.Logger.WithFields(
				errs.LogField{Key: "roomID", Val: roomID},
				errs.LogField{Key: "msgID", Val: msg.MsgID.String()},
				errs.LogField{Key: "error", Val: err.Error()},
			).Error(fmt.Sprintf("failed to publish message to Centrifugo: %v", err))
		}
	}
}

// logError 統一的錯誤記錄方法
func (use *MessageUseCase) logError(funcName string, param interface{}, err error, message string) error {
	return errs.DBErrorErrorL(
		use.Logger,
		[]errs.LogField{
			{Key: "param", Val: param},
			{Key: "func", Val: funcName},
			{Key: "err", Val: err.Error()},
		},
		message)
}

func (use *MessageUseCase) ListMessages(ctx context.Context, req usecase.ListMessagesReq) ([]usecase.Message, int64, error) {
	// 驗證輸入參數
	if err := use.validateListMessagesReq(req); err != nil {
		return nil, 0, err
	}

	// 驗證使用者是否在房間中
	if err := use.verifyRoomMembership(ctx, req.UID, req.RoomID); err != nil {
		return nil, 0, err
	}

	// 取得 bucket_day（如果未提供則使用今天）
	bucketDay := req.BucketDay
	if bucketDay == "" {
		bucketDay = utils.GetTodayBucketDay()
	}

	// 防止 PageSize overflow 並設置合理的範圍
	pageSize := use.normalizePageSize(req.PageSize)

	// 查詢訊息
	messages, err := use.MessageRepo.ListMessages(ctx, repository.ListMessagesReq{
		RoomID:    req.RoomID,
		BucketDay: bucketDay,
		PageSize:  pageSize,
		LastTS:    req.LastTS,
	})
	if err != nil {
		return nil, 0, use.logError("messageRepo.ListMessages", req, err, "failed to list messages")
	}

	// 轉換為 usecase.Message
	result := make([]usecase.Message, 0, len(messages))
	for _, msg := range messages {
		result = append(result, usecase.Message{
			RoomID:    msg.RoomID.String(),
			BucketDay: msg.BucketDay,
			TS:        msg.TS,
			UID:       msg.UID,
			Content:   msg.Content,
		})
	}

	// 計算總數（只在第一頁時計算）
	var total int64
	if req.LastTS == 0 {
		total, err = use.MessageRepo.Count(ctx, req.RoomID)
		if err != nil {
			return nil, 0, use.logError("messageRepo.Count", req, err, "failed to count messages")
		}
	}

	return result, total, nil
}

// validateListMessagesReq 驗證查詢訊息的請求參數
func (use *MessageUseCase) validateListMessagesReq(req usecase.ListMessagesReq) error {
	if req.RoomID == "" {
		return errs.InputInvalidFormatError("room_id cannot be empty")
	}
	if req.UID == "" {
		return errs.InputInvalidFormatError("uid cannot be empty")
	}
	return nil
}

// normalizePageSize 正規化分頁大小
func (use *MessageUseCase) normalizePageSize(pageSize int64) int {
	// 檢查是否超過 int 的最大值
	if pageSize > int64(math.MaxInt) {
		return maxPageSize
	}

	size := int(pageSize)
	if size <= 0 {
		return defaultPageSize
	}

	if size > maxPageSize {
		return maxPageSize
	}

	return size
}

// calculateMD5 計算字串的 MD5 雜湊值
func calculateMD5(content string) string {
	hash := md5.Sum([]byte(content))
	return hex.EncodeToString(hash[:])
}
