package chat

import (
	"context"
	"fmt"
	"gorm.io/gorm"
	"hfb_sys/backend/internal/model"
	"strings"
	"time"
)

const (
	adminChatFilterAll        = "all"
	adminChatFilterMine       = "mine"
	adminChatFilterUnassigned = "unassigned"

	adminChatStageAll       = "all"
	adminChatStagePending   = "pending"
	adminChatStageUnjoined  = "unjoined"
	adminChatStageHandoff   = "handoff"
	adminChatStageRenting   = "renting"
	adminChatStageAfterSale = "after_sale"
	adminChatStageEnded     = "ended"
)

var adminChatFilters = []string{adminChatFilterMine, adminChatFilterAll, adminChatFilterUnassigned}
var adminChatStages = []string{adminChatStageAll, adminChatStagePending, adminChatStageUnjoined, adminChatStageHandoff, adminChatStageRenting, adminChatStageAfterSale, adminChatStageEnded}

type AdminConversationCountsDTO struct {
	Ownership map[string]int64 `json:"ownership"`
	Stages    map[string]int64 `json:"stages"`
}

func (r *Repository) TransferConversation(ctx context.Context, principal Principal, conversationID uint64, toAdminID uint64) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 验证当前操作者是会话参与者
		current, err := r.findParticipant(tx, principal, conversationID, false)
		if err != nil {
			return err
		}
		if current.Role != "support" || current.ParticipantType != "admin" {
			return ErrPermissionDenied
		}
		// 验证目标客服存在、活跃且拥有客服角色，避免转接给超级管理员。
		if !adminIsSupport(tx, toAdminID) {
			return fmt.Errorf("目标客服不存在、已禁用或不是客服角色")
		}
		// 检查目标客服是否已有该会话
		var count int64
		if err := tx.Model(&model.ChatParticipant{}).
			Where("conversation_id = ? AND participant_type = ? AND participant_id = ?", conversationID, "admin", toAdminID).
			Count(&count).Error; err != nil {
			return err
		}
		if count > 0 {
			return fmt.Errorf("该客服已在会话中")
		}
		// 只转接当前客服本人，避免误删同群里的收号组/卖号组其他客服。
		if err := tx.Model(&model.ChatParticipant{}).
			Where("id = ?", current.ID).
			Updates(map[string]interface{}{
				"participant_id": toAdminID,
				"joined_at":      time.Now(),
			}).Error; err != nil {
			return err
		}
		// 添加系统消息记录转接
		message := model.ChatMessage{
			ConversationID: conversationID,
			SenderType:     "system",
			SenderRole:     "system",
			ContentType:    "system",
			Content:        "会话已转接给其他客服",
			AttachmentURLS: emptyJSONList(),
		}
		if err := tx.Create(&message).Error; err != nil {
			return err
		}
		return nil
	})
}
func (r *Repository) GetAvailableSupportAdmins(ctx context.Context) ([]SupportAdminDTO, error) {
	db := r.db.WithContext(ctx)
	// 仅展示客服角色管理员，超级管理员即使有 chat:view 权限也不作为客服候选。
	type adminRow struct {
		ID            uint64
		Nickname      string
		SupportStatus string
	}
	var admins []adminRow
	err := db.Table("admin_users AS au").
		Select("au.id, COALESCE(NULLIF(au.nickname, ''), au.username) AS nickname, au.support_status").
		Joins("JOIN admin_user_roles AS aur ON aur.admin_user_id = au.id").
		Joins("JOIN roles AS r ON r.id = aur.role_id").
		Where("au.status = ? AND r.code = ?", "active", defaultSupportRoleCode).
		Order("CASE au.support_status WHEN 'online' THEN 0 WHEN 'busy' THEN 1 ELSE 2 END, au.id ASC").
		Scan(&admins).Error
	if err != nil {
		return nil, err
	}

	// 统计每个客服的会话数
	type loadRow struct {
		AdminID uint64
		Count   int64
	}
	var loads []loadRow
	adminIDs := make([]uint64, len(admins))
	for i, a := range admins {
		adminIDs[i] = a.ID
	}
	if len(adminIDs) > 0 {
		db.Table("chat_participants").
			Select("participant_id AS admin_id, COUNT(*) AS count").
			Where("participant_type = ? AND role = ? AND participant_id IN ?", "admin", "support", adminIDs).
			Group("participant_id").
			Scan(&loads)
	}
	loadMap := make(map[uint64]int64)
	for _, l := range loads {
		loadMap[l.AdminID] = l.Count
	}

	result := make([]SupportAdminDTO, len(admins))
	for i, a := range admins {
		result[i] = SupportAdminDTO{
			ID:            a.ID,
			Nickname:      a.Nickname,
			SupportStatus: a.SupportStatus,
			ChatCount:     loadMap[a.ID],
		}
	}
	return result, nil
}
func (r *Repository) ListConversationsWithFilter(ctx context.Context, principal Principal, page, pageSize int, filter string, stage string, keyword string) (*PaginatedResult, error) {
	page, pageSize = normalizePagination(page, pageSize)
	filter = normalizeAdminChatFilter(filter)
	stage = normalizeAdminChatStage(stage)
	keyword = strings.TrimSpace(keyword)

	if principal.Type == "admin" {
		return r.listAdminConversations(ctx, principal, page, pageSize, filter, stage, keyword)
	}

	// 其他情况使用原有逻辑
	var total int64
	db := r.db.WithContext(ctx)
	countDB := db.Table("chat_conversations AS c").
		Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id")

	switch filter {
	case "mine":
		// 只看我的会话
		countDB = countDB.Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID)
	case "unassigned":
		// 未分配客服的会话
		countDB = countDB.Where("c.id NOT IN (?)",
			db.Table("chat_participants").Select("conversation_id").Where("participant_type = ? AND role = ?", "admin", "support"))
	default:
		// 普通用户的全部会话
		countDB = countDB.Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID)
	}

	if err := countDB.Count(&total).Error; err != nil {
		return nil, err
	}

	var rows []conversationRow
	offset := (page - 1) * pageSize

	queryDB := r.conversationQuery(ctx, principal)
	switch filter {
	case "mine":
		queryDB = queryDB.Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID)
	case "unassigned":
		queryDB = queryDB.Where("c.id NOT IN (?)",
			db.Table("chat_participants").Select("conversation_id").Where("participant_type = ? AND role = ?", "admin", "support"))
	default:
		// 普通用户的全部会话
		queryDB = queryDB.Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID)
	}

	err := queryDB.
		Order("COALESCE(c.last_message_at, c.created_at) DESC, c.id DESC").
		Offset(offset).
		Limit(pageSize).
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}

	items := make([]ConversationDTO, 0, len(rows))
	for _, row := range rows {
		participants, err := r.participants(ctx, row.ID)
		if err != nil {
			return nil, err
		}
		items = append(items, row.toDTO(participants))
	}
	return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
}

func (r *Repository) listAdminConversations(ctx context.Context, principal Principal, page, pageSize int, filter string, stage string, keyword string) (*PaginatedResult, error) {
	var total int64
	countDB := r.adminConversationBase(ctx, principal, keyword)
	applyAdminChatOwnershipFilter(countDB, filter, principal)
	applyAdminChatStageFilter(countDB, stage, principal)
	if err := countDB.Count(&total).Error; err != nil {
		return nil, err
	}

	var rows []conversationRow
	offset := (page - 1) * pageSize
	queryDB := r.adminConversationBase(ctx, principal, keyword).
		Select(`c.id, c.order_id, c.listing_id, c.type, c.title, c.status, c.last_message_id,
			c.last_message_preview, c.last_message_at, c.created_at, c.updated_at,
			COALESCE(cp_me.role, 'admin') AS role,
			lo.id AS latest_order_id, lo.order_no AS latest_order_no, lo.status AS latest_order_status,
			lo.handoff_status AS latest_handoff_status, lo.refund_status AS latest_refund_status,
			lm.sender_type AS last_sender_type, lm.sender_id AS last_sender_id, lm.sender_role AS last_sender_role,
			CASE WHEN cp_me.id IS NULL THEN 0 ELSE (
				SELECT COUNT(1)
				FROM chat_messages AS cm
				WHERE cm.conversation_id = c.id
				  AND NOT (cm.sender_type = ? AND cm.sender_id = ?)
				  AND (cp_me.last_read_at IS NULL OR cm.created_at > cp_me.last_read_at)
			) END AS unread_count`, principal.Type, principal.ID)
	applyAdminChatOwnershipFilter(queryDB, filter, principal)
	applyAdminChatStageFilter(queryDB, stage, principal)
	if err := queryDB.
		Order("COALESCE(c.last_message_at, c.created_at) DESC, c.id DESC").
		Offset(offset).
		Limit(pageSize).
		Scan(&rows).Error; err != nil {
		return nil, err
	}

	items := make([]ConversationDTO, 0, len(rows))
	for _, row := range rows {
		participants, err := r.participants(ctx, row.ID)
		if err != nil {
			return nil, err
		}
		items = append(items, row.toDTO(participants))
	}

	counts, err := r.adminConversationCounts(ctx, principal, filter, stage, keyword)
	if err != nil {
		return nil, err
	}
	return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize, Counts: counts}, nil
}

func (r *Repository) adminConversationBase(ctx context.Context, principal Principal, keyword string) *gorm.DB {
	db := r.db.WithContext(ctx).Table("chat_conversations AS c").
		Joins("LEFT JOIN chat_participants AS cp_me ON cp_me.conversation_id = c.id AND cp_me.participant_type = ? AND cp_me.participant_id = ?", principal.Type, principal.ID).
		Joins("LEFT JOIN chat_messages AS lm ON lm.id = c.last_message_id").
		Joins("LEFT JOIN rental_orders AS lo ON lo.id = COALESCE(c.order_id, (SELECT ro.id FROM rental_orders AS ro WHERE ro.listing_id = c.listing_id ORDER BY ro.id DESC LIMIT 1))").
		Joins("LEFT JOIN rental_listings AS l ON l.id = COALESCE(lo.listing_id, c.listing_id)").
		Joins("LEFT JOIN users AS renter ON renter.id = lo.renter_id").
		Joins("LEFT JOIN users AS owner ON owner.id = COALESCE(lo.owner_id, l.owner_id)")
	if keyword != "" {
		like := "%" + keyword + "%"
		db = db.Where(`c.title LIKE ? OR c.last_message_preview LIKE ? OR lo.order_no LIKE ? OR l.listing_no LIKE ?
			OR renter.phone LIKE ? OR owner.phone LIKE ?
			OR EXISTS (
				SELECT 1 FROM chat_participants AS cp_kw
				WHERE cp_kw.conversation_id = c.id AND cp_kw.remark LIKE ?
			)`, like, like, like, like, like, like, like)
	}
	return db
}

func (r *Repository) adminConversationCounts(ctx context.Context, principal Principal, filter string, stage string, keyword string) (*AdminConversationCountsDTO, error) {
	counts := &AdminConversationCountsDTO{
		Ownership: make(map[string]int64, len(adminChatFilters)),
		Stages:    make(map[string]int64, len(adminChatStages)),
	}
	for _, item := range adminChatFilters {
		db := r.adminConversationBase(ctx, principal, keyword)
		applyAdminChatOwnershipFilter(db, item, principal)
		applyAdminChatStageFilter(db, stage, principal)
		var count int64
		if err := db.Count(&count).Error; err != nil {
			return nil, err
		}
		counts.Ownership[item] = count
	}
	for _, item := range adminChatStages {
		db := r.adminConversationBase(ctx, principal, keyword)
		applyAdminChatOwnershipFilter(db, filter, principal)
		applyAdminChatStageFilter(db, item, principal)
		var count int64
		if err := db.Count(&count).Error; err != nil {
			return nil, err
		}
		counts.Stages[item] = count
	}
	return counts, nil
}

func normalizeAdminChatFilter(filter string) string {
	switch filter {
	case adminChatFilterAll, adminChatFilterMine, adminChatFilterUnassigned:
		return filter
	default:
		return adminChatFilterMine
	}
}

func normalizeAdminChatStage(stage string) string {
	switch stage {
	case adminChatStageAll, adminChatStagePending, adminChatStageUnjoined, adminChatStageHandoff, adminChatStageRenting, adminChatStageAfterSale, adminChatStageEnded:
		return stage
	default:
		return adminChatStageAll
	}
}

func applyAdminChatOwnershipFilter(db *gorm.DB, filter string, principal Principal) {
	switch filter {
	case adminChatFilterMine:
		db.Where("cp_me.id IS NOT NULL")
	case adminChatFilterUnassigned:
		db.Where(`NOT EXISTS (
			SELECT 1 FROM chat_participants AS cp_support
			WHERE cp_support.conversation_id = c.id
			  AND cp_support.participant_type = ?
			  AND cp_support.role = ?
		)`, "admin", "support")
	case adminChatFilterAll:
		return
	default:
		db.Where("cp_me.participant_type = ? AND cp_me.participant_id = ?", principal.Type, principal.ID)
	}
}

func applyAdminChatStageFilter(db *gorm.DB, stage string, principal Principal) {
	switch stage {
	case adminChatStagePending:
		db.Where(`(
			lm.sender_type = ?
			OR (
				cp_me.id IS NOT NULL
				AND EXISTS (
					SELECT 1
					FROM chat_messages AS cm_pending
					WHERE cm_pending.conversation_id = c.id
					  AND NOT (cm_pending.sender_type = ? AND cm_pending.sender_id = ?)
					  AND (cp_me.last_read_at IS NULL OR cm_pending.created_at > cp_me.last_read_at)
				)
			)
		)`, "user", principal.Type, principal.ID)
	case adminChatStageUnjoined:
		db.Where("lo.id IS NULL")
	case adminChatStageHandoff:
		db.Where("lo.status = ?", "pending_handoff")
	case adminChatStageRenting:
		db.Where("lo.status IN ?", []string{"renting", "overdue"})
	case adminChatStageAfterSale:
		db.Where("(lo.status IN ? OR (lo.refund_status IS NOT NULL AND lo.refund_status <> ?))",
			[]string{"pending_checkout_confirm", "pending_checkout_accept", "checkout_disputing", "abnormal"}, "none")
	case adminChatStageEnded:
		db.Where("(c.status IN ? OR lo.status IN ?)", []string{"archived", "closed"}, []string{"completed", "cancelled", "closed"})
	case adminChatStageAll:
		return
	}
}
