package report

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

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

const (
	targetTypeUser = "user"
	targetTypeRoom = "room"
)

type Service struct {
	roomClient roomclient.Client
	userDB     *sql.DB
}

func NewService(userDB *sql.DB, roomClient roomclient.Client) *Service {
	return &Service{userDB: userDB, roomClient: roomClient}
}

func (s *Service) ListReports(ctx context.Context, appCode string, query listQuery) ([]reportDTO, int64, error) {
	query = normalizeListQuery(query)
	if s == nil || s.userDB == nil {
		return nil, 0, fmt.Errorf("user mysql is not configured")
	}

	whereSQL, args := reportWhere(appCode, query)
	var total int64
	if err := s.userDB.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM user_reports r
		LEFT JOIN users u ON u.app_code = r.app_code AND u.user_id = r.target_user_id
		`+whereSQL,
		args...,
	).Scan(&total); err != nil {
		return nil, 0, err
	}

	rows, err := s.userDB.QueryContext(ctx, `
		SELECT r.report_id, r.reporter_user_id, r.target_type, r.target_user_id, r.room_id,
		       r.report_type, r.reason, CAST(r.image_urls_json AS CHAR), r.status, r.request_id,
		       r.created_at_ms, r.updated_at_ms,
		       COALESCE(u.current_display_user_id, ''), COALESCE(u.default_display_user_id, ''),
		       COALESCE(u.username, ''), COALESCE(u.avatar, '')
		FROM user_reports r
		LEFT JOIN users u ON u.app_code = r.app_code AND u.user_id = r.target_user_id
		`+whereSQL+`
		ORDER BY r.created_at_ms DESC, r.report_id DESC
		LIMIT ? OFFSET ?`,
		append(args, query.PageSize, offset(query.Page, query.PageSize))...,
	)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()

	items := make([]reportDTO, 0, query.PageSize)
	for rows.Next() {
		var item reportDTO
		var reporterUserID int64
		var targetUserID sql.NullInt64
		var imageURLsJSON string
		var displayUserID string
		var defaultDisplayUserID string
		var username string
		var avatar string
		if err := rows.Scan(
			&item.ReportID,
			&reporterUserID,
			&item.TargetType,
			&targetUserID,
			&item.RoomID,
			&item.ReportType,
			&item.Reason,
			&imageURLsJSON,
			&item.Status,
			&item.RequestID,
			&item.CreatedAtMS,
			&item.UpdatedAtMS,
			&displayUserID,
			&defaultDisplayUserID,
			&username,
			&avatar,
		); err != nil {
			return nil, 0, err
		}
		imageURLs, err := parseImageURLs(imageURLsJSON)
		if err != nil {
			return nil, 0, err
		}
		item.ReporterUserID = formatID(reporterUserID)
		item.ImageURLs = imageURLs
		item.Target = reportTargetDTO{Type: item.TargetType}
		if targetUserID.Valid && targetUserID.Int64 > 0 {
			item.UserID = formatID(targetUserID.Int64)
			item.Target.User = &reportUserDTO{
				Avatar:               avatar,
				DefaultDisplayUserID: defaultDisplayUserID,
				DisplayUserID:        displayUserID,
				UserID:               item.UserID,
				Username:             username,
			}
		}
		if item.TargetType == targetTypeRoom {
			item.Target.Room = &reportRoomDTO{RoomID: item.RoomID}
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}

	if err := s.fillRoomTargets(ctx, items); err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

func normalizeListQuery(query listQuery) listQuery {
	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.TargetType = normalizeTargetType(query.TargetType)
	query.ReportType = strings.ToLower(strings.TrimSpace(query.ReportType))
	if query.StartAtMS < 0 {
		query.StartAtMS = 0
	}
	if query.EndAtMS < 0 {
		query.EndAtMS = 0
	}
	return query
}

func normalizeTargetType(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case targetTypeUser, "用户":
		return targetTypeUser
	case targetTypeRoom, "房间":
		return targetTypeRoom
	default:
		return ""
	}
}

func reportWhere(appCode string, query listQuery) (string, []any) {
	conditions := []string{"r.app_code = ?"}
	args := []any{appCode}
	if query.TargetType != "" {
		conditions = append(conditions, "r.target_type = ?")
		args = append(args, query.TargetType)
	}
	if query.ReportType != "" {
		conditions = append(conditions, "r.report_type = ?")
		args = append(args, query.ReportType)
	}
	if query.StartAtMS > 0 {
		conditions = append(conditions, "r.created_at_ms >= ?")
		args = append(args, query.StartAtMS)
	}
	if query.EndAtMS > 0 {
		conditions = append(conditions, "r.created_at_ms < ?")
		args = append(args, query.EndAtMS)
	}
	if query.Keyword != "" {
		like := "%" + query.Keyword + "%"
		targetSQL, targetArgs := shared.UserIdentityKeywordSQL("u", "r.target_user_id", query.Keyword, time.Now().UnixMilli())
		conditions = append(conditions, `(r.report_id LIKE ? OR r.room_id LIKE ? OR CAST(r.reporter_user_id AS CHAR) LIKE ? OR `+targetSQL+`)`)
		args = append(args, like, like, like)
		args = append(args, targetArgs...)
	}
	return "WHERE " + strings.Join(conditions, " AND "), args
}

func parseImageURLs(raw string) ([]string, error) {
	if strings.TrimSpace(raw) == "" {
		return []string{}, nil
	}
	var urls []string
	if err := json.Unmarshal([]byte(raw), &urls); err != nil {
		return nil, err
	}
	if urls == nil {
		return []string{}, nil
	}
	return urls, nil
}

func (s *Service) fillRoomTargets(ctx context.Context, items []reportDTO) error {
	if s == nil || s.roomClient == nil || len(items) == 0 {
		return nil
	}
	roomIDs := make([]string, 0, len(items))
	for _, item := range items {
		if item.TargetType == targetTypeRoom && strings.TrimSpace(item.RoomID) != "" {
			roomIDs = append(roomIDs, item.RoomID)
		}
	}
	rooms, err := s.queryRooms(ctx, uniqueStrings(roomIDs))
	if err != nil {
		return err
	}
	for index := range items {
		if items[index].TargetType != targetTypeRoom {
			continue
		}
		room := reportRoomDTO{RoomID: items[index].RoomID}
		if found, ok := rooms[items[index].RoomID]; ok {
			room = found
		}
		items[index].Target.Room = &room
	}
	return nil
}

func (s *Service) queryRooms(ctx context.Context, roomIDs []string) (map[string]reportRoomDTO, error) {
	rooms := make(map[string]reportRoomDTO, len(roomIDs))
	for _, roomID := range roomIDs {
		room, err := s.roomClient.GetRoom(ctx, roomclient.GetRoomRequest{RoomID: roomID})
		if err != nil {
			// 举报列表是治理入口，房间被删或房间服务短暂不可读时仍保留举报事实本身。
			if ctx.Err() != nil {
				return nil, ctx.Err()
			}
			continue
		}
		rooms[roomID] = reportRoomDTO{
			CoverURL:    room.CoverURL,
			RoomID:      room.RoomID,
			RoomShortID: room.RoomShortID,
			Title:       room.Title,
		}
	}
	return rooms, nil
}

func formatID(value int64) string {
	if value <= 0 {
		return ""
	}
	return strconv.FormatInt(value, 10)
}

func uniqueStrings(values []string) []string {
	seen := make(map[string]struct{}, len(values))
	unique := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		unique = append(unique, value)
	}
	return unique
}

func offset(page int, pageSize int) int {
	return (page - 1) * pageSize
}
