package roomadmin

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

	"hyapp-admin-server/internal/integration/roomclient"
)

const (
	roomPinStatusActive    = "active"
	roomPinStatusCancelled = "cancelled"
	roomPinStatusExpired   = "expired"
	roomPinStatusAll       = "all"
	roomPinTypeRegion      = "region"
	roomPinTypeGlobal      = "global"
	roomPinTypeCountry     = "country"
	defaultRoomPinDays     = int64(30)
	maxRoomPinDays         = int64(3650)
	roomPinDayMillis       = int64(24 * 60 * 60 * 1000)
)

type RoomPinRoom struct {
	CoverURL        string `json:"coverUrl"`
	RegionName      string `json:"regionName"`
	RoomID          string `json:"roomId"`
	RoomShortID     string `json:"roomShortId"`
	Status          string `json:"status"`
	Title           string `json:"title"`
	VisibleRegionID int64  `json:"visibleRegionId"`
}

type RoomPin struct {
	CancelledAtMS      int64       `json:"cancelledAtMs"`
	CancelledByAdminID uint        `json:"cancelledByAdminId"`
	CreatedAtMS        int64       `json:"createdAtMs"`
	CreatedByAdminID   uint        `json:"createdByAdminId"`
	ExpiresAtMS        int64       `json:"expiresAtMs"`
	ID                 int64       `json:"id"`
	PinType            string      `json:"pinType"`
	PinnedAtMS         int64       `json:"pinnedAtMs"`
	RemainingMS        int64       `json:"remainingMs"`
	Room               RoomPinRoom `json:"room"`
	Status             string      `json:"status"`
	UpdatedAtMS        int64       `json:"updatedAtMs"`
	User               RoomOwner   `json:"user"`
	Weight             int64       `json:"weight"`
}

func (s *Service) ListRoomPins(ctx context.Context, query roomPinListQuery) ([]RoomPin, int64, error) {
	if s.roomClient == nil {
		return nil, 0, fmt.Errorf("room service client is not configured")
	}
	query = normalizeRoomPinListQuery(query)
	result, err := s.roomClient.ListRoomPins(ctx, roomclient.ListRoomPinsRequest{
		Page:     query.Page,
		PageSize: query.PageSize,
		Keyword:  query.Keyword,
		Status:   query.Status,
		RegionID: query.RegionID,
		PinType:  query.PinType,
	})
	if err != nil {
		return nil, 0, err
	}
	items := roomPinsFromClient(result.Pins)
	if err := s.fillRoomPinDetails(ctx, items); err != nil {
		return nil, 0, err
	}
	return items, result.Total, nil
}

func (s *Service) CreateRoomPin(ctx context.Context, req createRoomPinRequest, actorID uint) (RoomPin, error) {
	if s.roomClient == nil {
		return RoomPin{}, fmt.Errorf("room service client is not configured")
	}
	input, err := normalizeCreateRoomPinRequest(req)
	if err != nil {
		return RoomPin{}, err
	}
	pin, err := s.roomClient.CreateRoomPin(ctx, roomclient.CreateRoomPinRequest{
		RoomID:       input.RoomID,
		PinType:      input.PinType,
		Weight:       input.Weight,
		DurationDays: input.DurationDays,
		PinnedAtMS:   input.PinnedAtMS,
		ExpiresAtMS:  input.ExpiresAtMS,
		AdminID:      uint64(actorID),
	})
	if err != nil {
		return RoomPin{}, mapRoomClientError(err)
	}
	item := roomPinFromClient(pin)
	items := []RoomPin{item}
	if err := s.fillRoomPinDetails(ctx, items); err != nil {
		return RoomPin{}, err
	}
	return items[0], nil
}

func (s *Service) CancelRoomPin(ctx context.Context, pinID int64, actorID uint) (RoomPin, error) {
	if s.roomClient == nil {
		return RoomPin{}, fmt.Errorf("room service client is not configured")
	}
	if pinID <= 0 {
		return RoomPin{}, fmt.Errorf("%w: 置顶 ID 不正确", ErrInvalidArgument)
	}
	pin, err := s.roomClient.CancelRoomPin(ctx, roomclient.CancelRoomPinRequest{PinID: pinID, AdminID: uint64(actorID)})
	if err != nil {
		return RoomPin{}, mapRoomClientError(err)
	}
	item := roomPinFromClient(pin)
	items := []RoomPin{item}
	if err := s.fillRoomPinDetails(ctx, items); err != nil {
		return RoomPin{}, err
	}
	return items[0], nil
}

func normalizeRoomPinListQuery(query roomPinListQuery) roomPinListQuery {
	if query.Page < 1 {
		query.Page = 1
	}
	if query.PageSize < 1 {
		query.PageSize = 20
	}
	if query.PageSize > 100 {
		query.PageSize = 100
	}
	query.Keyword = strings.TrimSpace(query.Keyword)
	query.Status = normalizeRoomPinStatus(query.Status)
	query.PinType = normalizeRoomPinType(query.PinType)
	if query.RegionID < 0 {
		query.RegionID = 0
	}
	return query
}

func normalizeRoomPinType(pinType string) string {
	switch strings.ToLower(strings.TrimSpace(pinType)) {
	case roomPinTypeGlobal:
		return roomPinTypeGlobal
	case roomPinTypeCountry:
		return roomPinTypeCountry
	case roomPinTypeRegion:
		return roomPinTypeRegion
	default:
		return ""
	}
}

func normalizeRoomPinStatus(status string) string {
	switch strings.ToLower(strings.TrimSpace(status)) {
	case "", roomPinStatusActive:
		return roomPinStatusActive
	case roomPinStatusExpired:
		return roomPinStatusExpired
	case roomPinStatusCancelled:
		return roomPinStatusCancelled
	case roomPinStatusAll:
		return roomPinStatusAll
	default:
		return roomPinStatusActive
	}
}

func normalizeCreateRoomPinRequest(req createRoomPinRequest) (createRoomPinRequest, error) {
	req.RoomID = strings.TrimSpace(req.RoomID)
	if req.RoomID == "" || len(req.RoomID) > 64 {
		return req, fmt.Errorf("%w: 请选择房间", ErrInvalidArgument)
	}
	if req.Weight < 0 {
		return req, fmt.Errorf("%w: 权重不能小于 0", ErrInvalidArgument)
	}
	req.PinType = normalizeRoomPinType(req.PinType)
	if req.PinType == "" {
		req.PinType = roomPinTypeRegion
	}
	if req.PinnedAtMS <= 0 {
		req.PinnedAtMS = time.Now().UTC().UnixMilli()
	}
	if req.ExpiresAtMS <= 0 && req.DurationDays == 0 {
		req.DurationDays = defaultRoomPinDays
	}
	if req.ExpiresAtMS <= 0 {
		req.ExpiresAtMS = req.PinnedAtMS + req.DurationDays*roomPinDayMillis
	}
	if req.ExpiresAtMS <= req.PinnedAtMS {
		return req, fmt.Errorf("%w: 置顶时间区间不正确", ErrInvalidArgument)
	}
	durationDays := (req.ExpiresAtMS - req.PinnedAtMS + roomPinDayMillis - 1) / roomPinDayMillis
	if durationDays < 1 || durationDays > maxRoomPinDays {
		return req, fmt.Errorf("%w: 置顶时间必须在 1 到 3650 天之间", ErrInvalidArgument)
	}
	req.DurationDays = durationDays
	return req, nil
}

func roomPinsFromClient(input []roomclient.RoomPin) []RoomPin {
	out := make([]RoomPin, 0, len(input))
	for _, pin := range input {
		out = append(out, roomPinFromClient(pin))
	}
	return out
}

func roomPinFromClient(input roomclient.RoomPin) RoomPin {
	status := input.Room.Status
	if status != "active" {
		status = "closed"
	}
	ownerUserID := strconv.FormatInt(input.Room.OwnerUserID, 10)
	remainingMS := input.ExpiresAtMS - time.Now().UTC().UnixMilli()
	if remainingMS < 0 {
		remainingMS = 0
	}
	return RoomPin{
		ID:                 input.ID,
		PinType:            firstNonEmpty(input.PinType, roomPinTypeRegion),
		Weight:             input.Weight,
		Status:             input.Status,
		PinnedAtMS:         input.PinnedAtMS,
		ExpiresAtMS:        input.ExpiresAtMS,
		CancelledAtMS:      input.CancelledAtMS,
		CreatedByAdminID:   uint(input.CreatedByAdminID),
		CancelledByAdminID: uint(input.CancelledByAdminID),
		CreatedAtMS:        input.CreatedAtMS,
		UpdatedAtMS:        input.UpdatedAtMS,
		RemainingMS:        remainingMS,
		User:               RoomOwner{UserID: ownerUserID},
		Room: RoomPinRoom{
			RoomID:          input.Room.RoomID,
			RoomShortID:     input.Room.RoomShortID,
			VisibleRegionID: input.Room.VisibleRegionID,
			Title:           input.Room.Title,
			CoverURL:        input.Room.CoverURL,
			Status:          status,
		},
	}
}

type roomPinScanner interface {
	Scan(dest ...any) error
}

func scanRoomPin(row roomPinScanner) (RoomPin, error) {
	var item RoomPin
	var ownerUserID int64
	err := row.Scan(
		&item.ID,
		&item.Room.VisibleRegionID,
		&item.Room.RoomID,
		&item.Weight,
		&item.Status,
		&item.PinnedAtMS,
		&item.ExpiresAtMS,
		&item.CancelledAtMS,
		&item.CreatedByAdminID,
		&item.CancelledByAdminID,
		&item.CreatedAtMS,
		&item.UpdatedAtMS,
		&item.Room.RoomShortID,
		&item.Room.Title,
		&item.Room.CoverURL,
		&ownerUserID,
		&item.Room.Status,
	)
	item.User = RoomOwner{UserID: strconv.FormatInt(ownerUserID, 10)}
	item.RemainingMS = item.ExpiresAtMS - time.Now().UTC().UnixMilli()
	if item.RemainingMS < 0 {
		item.RemainingMS = 0
	}
	if item.Room.Status != "active" {
		item.Room.Status = "closed"
	}
	return item, err
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

func (s *Service) fillRoomPinDetails(ctx context.Context, items []RoomPin) error {
	if s.userDB == nil || len(items) == 0 {
		return nil
	}

	ownerIDs := make([]int64, 0, len(items))
	regionIDs := make([]int64, 0, len(items))
	for _, item := range items {
		if ownerID, err := strconv.ParseInt(item.User.UserID, 10, 64); err == nil && ownerID > 0 {
			ownerIDs = append(ownerIDs, ownerID)
		}
		if item.Room.VisibleRegionID > 0 {
			regionIDs = append(regionIDs, item.Room.VisibleRegionID)
		}
	}

	owners, err := s.queryRoomOwners(ctx, uniqueInt64s(ownerIDs))
	if err != nil {
		return err
	}
	regions, err := s.queryRegionNames(ctx, uniqueInt64s(regionIDs))
	if err != nil {
		return err
	}

	for index := range items {
		if ownerID, err := strconv.ParseInt(items[index].User.UserID, 10, 64); err == nil {
			if owner, ok := owners[ownerID]; ok {
				items[index].User = owner
			}
		}
		if name, ok := regions[items[index].Room.VisibleRegionID]; ok {
			items[index].Room.RegionName = name
		}
	}
	return nil
}
