package supportgroup

import (
	"context"
	"errors"
	"strings"

	"hfb_sys/backend/internal/model"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type Repository struct {
	db *gorm.DB
}

func NewRepository(db *gorm.DB) *Repository {
	return &Repository{db: db}
}

func (r *Repository) List(ctx context.Context) ([]GroupDTO, error) {
	var groups []model.ChatSupportGroup
	if err := r.db.WithContext(ctx).Order("sort_order ASC, id ASC").Find(&groups).Error; err != nil {
		return nil, err
	}
	items := make([]GroupDTO, 0, len(groups))
	for _, group := range groups {
		dto, err := r.toDTO(ctx, group)
		if err != nil {
			return nil, err
		}
		items = append(items, dto)
	}
	return items, nil
}

func (r *Repository) FindByID(ctx context.Context, id uint64) (*GroupDTO, error) {
	var group model.ChatSupportGroup
	if err := r.db.WithContext(ctx).First(&group, id).Error; err != nil {
		return nil, err
	}
	dto, err := r.toDTO(ctx, group)
	if err != nil {
		return nil, err
	}
	return &dto, nil
}

func (r *Repository) Create(ctx context.Context, req CreateGroupRequest) (*GroupDTO, error) {
	code := strings.TrimSpace(req.Code)
	if code == "" {
		code = makeCustomCode(req.Name)
	}
	status := normalizeStatus(req.Status)
	group := model.ChatSupportGroup{
		Code:        code,
		Name:        strings.TrimSpace(req.Name),
		Description: strings.TrimSpace(req.Description),
		Status:      status,
		SortOrder:   req.SortOrder,
	}
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&group).Error; err != nil {
			return err
		}
		return replaceMembers(tx, group.ID, req.MemberIDs)
	})
	if err != nil {
		return nil, err
	}
	return r.FindByID(ctx, group.ID)
}

func (r *Repository) Update(ctx context.Context, id uint64, req UpdateGroupRequest) (*GroupDTO, error) {
	var group model.ChatSupportGroup
	db := r.db.WithContext(ctx)
	if err := db.First(&group, id).Error; err != nil {
		return nil, err
	}
	group.Name = strings.TrimSpace(req.Name)
	group.Description = strings.TrimSpace(req.Description)
	group.Status = normalizeStatus(req.Status)
	group.SortOrder = req.SortOrder
	err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Save(&group).Error; err != nil {
			return err
		}
		return replaceMembers(tx, group.ID, req.MemberIDs)
	})
	if err != nil {
		return nil, err
	}
	return r.FindByID(ctx, id)
}

func (r *Repository) Delete(ctx context.Context, id uint64) error {
	var group model.ChatSupportGroup
	db := r.db.WithContext(ctx)
	if err := db.First(&group, id).Error; err != nil {
		return err
	}
	if isProtectedCode(group.Code) {
		return ErrProtectedGroup
	}
	return db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("group_id = ?", id).Delete(&model.ChatSupportGroupMember{}).Error; err != nil {
			return err
		}
		return tx.Delete(&model.ChatSupportGroup{}, id).Error
	})
}

func (r *Repository) AssignMembers(ctx context.Context, id uint64, memberIDs []uint64) error {
	var count int64
	if err := r.db.WithContext(ctx).Model(&model.ChatSupportGroup{}).Where("id = ?", id).Count(&count).Error; err != nil {
		return err
	}
	if count == 0 {
		return gorm.ErrRecordNotFound
	}
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		return replaceMembers(tx, id, memberIDs)
	})
}

func (r *Repository) ListSupportAdmins(ctx context.Context) ([]MemberDTO, error) {
	var rows []struct {
		ID            uint64
		Username      string
		Nickname      string
		SupportStatus string
	}
	err := r.db.WithContext(ctx).Table("admin_users AS au").
		Select("DISTINCT au.id, au.username, au.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", "cs").
		Order("CASE au.support_status WHEN 'online' THEN 0 WHEN 'busy' THEN 1 ELSE 2 END, au.id ASC").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	items := make([]MemberDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, MemberDTO{
			ID:            row.ID,
			Username:      row.Username,
			Nickname:      row.Nickname,
			SupportStatus: row.SupportStatus,
		})
	}
	return items, nil
}

func (r *Repository) toDTO(ctx context.Context, group model.ChatSupportGroup) (GroupDTO, error) {
	members, err := r.members(ctx, group.ID)
	if err != nil {
		return GroupDTO{}, err
	}
	return GroupDTO{
		ID:          group.ID,
		Code:        group.Code,
		Name:        group.Name,
		Description: group.Description,
		Status:      group.Status,
		SortOrder:   group.SortOrder,
		Members:     members,
		CreatedAt:   group.CreatedAt,
		UpdatedAt:   group.UpdatedAt,
	}, nil
}

func (r *Repository) members(ctx context.Context, groupID uint64) ([]MemberDTO, error) {
	var rows []struct {
		ID            uint64
		Username      string
		Nickname      string
		SupportStatus string
	}
	err := r.db.WithContext(ctx).Table("chat_support_group_members AS gm").
		Select("au.id, au.username, au.nickname, au.support_status").
		Joins("JOIN admin_users AS au ON au.id = gm.admin_user_id").
		Where("gm.group_id = ?", groupID).
		Order("au.id ASC").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	items := make([]MemberDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, MemberDTO{
			ID:            row.ID,
			Username:      row.Username,
			Nickname:      row.Nickname,
			SupportStatus: row.SupportStatus,
		})
	}
	return items, nil
}

func replaceMembers(tx *gorm.DB, groupID uint64, memberIDs []uint64) error {
	if err := tx.Where("group_id = ?", groupID).Delete(&model.ChatSupportGroupMember{}).Error; err != nil {
		return err
	}
	for _, adminID := range uniqueIDs(memberIDs) {
		if adminID == 0 {
			continue
		}
		member := model.ChatSupportGroupMember{GroupID: groupID, AdminID: adminID}
		if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&member).Error; err != nil {
			return err
		}
	}
	return nil
}

func normalizeStatus(status string) string {
	switch status {
	case "disabled":
		return "disabled"
	default:
		return "active"
	}
}

func isProtectedCode(code string) bool {
	return code == GroupCodeOwnerOnboarding || code == GroupCodeRenterHandoff
}

func makeCustomCode(name string) string {
	base := strings.TrimSpace(strings.ToLower(name))
	base = strings.ReplaceAll(base, " ", "_")
	base = strings.ReplaceAll(base, "-", "_")
	if base == "" {
		base = "custom"
	}
	return "custom_" + base
}

func uniqueIDs(ids []uint64) []uint64 {
	seen := make(map[uint64]struct{}, len(ids))
	result := make([]uint64, 0, len(ids))
	for _, id := range ids {
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		result = append(result, id)
	}
	return result
}

func IsNotFound(err error) bool {
	return errors.Is(err, gorm.ErrRecordNotFound)
}
