// Package service 提供 admin 模块的业务服务（会议管理）
package service

import (
	"context"
	"errors"
	"time"

	adminDAO "github.com/echochat/backend/app/admin/dao"
	"github.com/echochat/backend/app/dto"
	meetingModel "github.com/echochat/backend/app/meeting/model"
	meetingService "github.com/echochat/backend/app/meeting/service"
	"github.com/echochat/backend/pkg/logs"
	"go.uber.org/zap"
)

// ErrMeetingNotFound 会议记录不存在
var ErrMeetingNotFound = errors.New("会议记录不存在")

// MeetingManageService 管理端会议查询服务
//
// 仅做"读"路径：列表 / 详情 / 统计。
// "强制结束会议" 这种写操作直接复用 meeting/service.MeetingService.EndRoom 即可（admin_force 路径），
// 暂不在本服务里重复包装，避免与现有 RBAC/中间件冲突。
type MeetingManageService struct {
	dao    *adminDAO.MeetingManageDAO
	recSvc *meetingService.MeetingRecordingService
}

// NewMeetingManageService Wire Provider
func NewMeetingManageService(
	dao *adminDAO.MeetingManageDAO,
	recSvc *meetingService.MeetingRecordingService,
) *MeetingManageService {
	return &MeetingManageService{dao: dao, recSvc: recSvc}
}

// ForceStopRecording 管理员强制结束指定录制
//
// 委托给 meeting/service.MeetingRecordingService.ForceStopByAdmin：
//   - 终态（ready/failed/uploading）幂等返回 nil
//   - 否则走完整 stop + 上传 MinIO + DB 状态机
func (s *MeetingManageService) ForceStopRecording(ctx context.Context, recordingID int64) error {
	funcName := "service.meeting_manage_service.ForceStopRecording"
	logs.Info(ctx, funcName, "管理员请求强制结束录制", zap.Int64("recording_id", recordingID))

	_, err := s.recSvc.ForceStopByAdmin(ctx, recordingID)
	return err
}

// ---------- public methods ----------

// GetMeetingList 查询会议列表（分页 + 多条件）
func (s *MeetingManageService) GetMeetingList(ctx context.Context, req *dto.AdminMeetingListRequest) (*dto.AdminMeetingListResponse, error) {
	funcName := "service.meeting_manage_service.GetMeetingList"
	logs.Debug(ctx, funcName, "获取会议列表")

	if req.Page <= 0 {
		req.Page = 1
	}
	if req.PageSize <= 0 {
		req.PageSize = 20
	}

	rows, total, err := s.dao.ListMeetings(ctx, req)
	if err != nil {
		return nil, err
	}

	list := make([]dto.AdminMeetingDTO, 0, len(rows))
	for i := range rows {
		list = append(list, rowToDTO(&rows[i]))
	}

	return &dto.AdminMeetingListResponse{
		Total:    total,
		List:     list,
		Page:     req.Page,
		PageSize: req.PageSize,
	}, nil
}

// GetMeetingDetail 查询会议详情（含参与者 + 录制）
func (s *MeetingManageService) GetMeetingDetail(ctx context.Context, id int64) (*dto.AdminMeetingDetailResponse, error) {
	funcName := "service.meeting_manage_service.GetMeetingDetail"
	logs.Debug(ctx, funcName, "获取会议详情", zap.Int64("id", id))

	row, err := s.dao.GetMeetingByID(ctx, id)
	if err != nil {
		return nil, err
	}
	if row == nil {
		return nil, ErrMeetingNotFound
	}

	participants, err := s.dao.ListParticipantsDetail(ctx, row.ID)
	if err != nil {
		logs.Warn(ctx, funcName, "拉取参与者明细失败（继续返回详情）",
			zap.Int64("room_id", row.ID), zap.Error(err))
		participants = nil
	}
	recordings, err := s.dao.ListRecordingsByRoom(ctx, row.ID)
	if err != nil {
		logs.Warn(ctx, funcName, "拉取录制列表失败（继续返回详情）",
			zap.Int64("room_id", row.ID), zap.Error(err))
		recordings = nil
	}

	pDTOs := make([]dto.AdminMeetingParticipantDTO, 0, len(participants))
	for i := range participants {
		pDTOs = append(pDTOs, participantToDTO(&participants[i]))
	}
	rDTOs := make([]dto.AdminMeetingRecordingDTO, 0, len(recordings))
	for i := range recordings {
		rDTOs = append(rDTOs, recordingToDTO(&recordings[i]))
	}

	return &dto.AdminMeetingDetailResponse{
		AdminMeetingDTO: rowToDTO(row),
		Participants:    pDTOs,
		Recordings:      rDTOs,
	}, nil
}

// GetStats 获取会议管理摘要统计（卡片）
func (s *MeetingManageService) GetStats(ctx context.Context) (*dto.AdminMeetingStatsResponse, error) {
	funcName := "service.meeting_manage_service.GetStats"
	logs.Debug(ctx, funcName, "获取会议统计")

	total, err := s.dao.CountTotal(ctx)
	if err != nil {
		return nil, err
	}
	active, err := s.dao.CountActive(ctx)
	if err != nil {
		return nil, err
	}
	today, err := s.dao.CountToday(ctx)
	if err != nil {
		return nil, err
	}
	recordings, err := s.dao.CountRecordings(ctx)
	if err != nil {
		return nil, err
	}

	return &dto.AdminMeetingStatsResponse{
		TotalCount:     total,
		ActiveCount:    active,
		TodayCount:     today,
		RecordingCount: recordings,
	}, nil
}

// ---------- DTO 投影辅助 ----------

const datetimeLayout = "2006-01-02 15:04:05"

// formatTimePtr 把 *time.Time 转成展示字符串；nil 返回空串
func formatTimePtr(t *time.Time) string {
	if t == nil {
		return ""
	}
	return t.Format(datetimeLayout)
}

// formatStrPtr 把 *string 转成普通 string；nil 返回空串
func formatStrPtr(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

// typeLabel 1=即时 2=预约
func typeLabel(t int) string {
	switch t {
	case 1:
		return "即时会议"
	case 2:
		return "预约会议"
	default:
		return "未知"
	}
}

// statusLabel 0=未开始 1=进行中 2=已结束（与 meeting_rooms.status 对齐）
func statusLabel(s int) string {
	switch s {
	case 0:
		return "未开始"
	case 1:
		return "进行中"
	case 2:
		return "已结束"
	default:
		return "未知"
	}
}

// roleLabel 0=普通 1=主持人 2=联合主持人
func roleLabel(r int) string {
	switch r {
	case 1:
		return "主持人"
	case 2:
		return "联合主持人"
	default:
		return "参会人"
	}
}

// computeDuration 计算会议时长（秒）
//   - 已结束：ended_at - started_at
//   - 进行中：now - started_at
//   - 未开始：0
func computeDuration(row *adminDAO.MeetingListRow) int {
	if row.StartedAt == nil {
		return 0
	}
	end := time.Now()
	if row.EndedAt != nil {
		end = *row.EndedAt
	}
	d := end.Sub(*row.StartedAt)
	if d < 0 {
		return 0
	}
	return int(d.Seconds())
}

func rowToDTO(row *adminDAO.MeetingListRow) dto.AdminMeetingDTO {
	return dto.AdminMeetingDTO{
		ID:               row.ID,
		RoomCode:         row.RoomCode,
		Title:            row.Title,
		Type:             row.Type,
		TypeLabel:        typeLabel(row.Type),
		Status:           row.Status,
		StatusLabel:      statusLabel(row.Status),
		HostID:           row.HostID,
		HostNickname:     row.HostNickname,
		HostAvatar:       row.HostAvatar,
		MaxMembers:       row.MaxMembers,
		ParticipantCount: row.ParticipantCount,
		RecordingCount:   row.RecordingCount,
		DurationSec:      computeDuration(row),
		ScheduledAt:      formatTimePtr(row.ScheduledAt),
		StartedAt:        formatTimePtr(row.StartedAt),
		EndedAt:          formatTimePtr(row.EndedAt),
		EndedReason:      formatStrPtr(row.EndedReason),
		CreatedAt:        row.CreatedAt.Format(datetimeLayout),
	}
}

func participantToDTO(p *adminDAO.ParticipantDetailRow) dto.AdminMeetingParticipantDTO {
	return dto.AdminMeetingParticipantDTO{
		UserID:     p.UserID,
		Nickname:   p.Nickname,
		Avatar:     p.Avatar,
		Role:       p.Role,
		RoleLabel:  roleLabel(p.Role),
		JoinedAt:   p.JoinedAt.Format(datetimeLayout),
		LeftAt:     formatTimePtr(p.LeftAt),
		LeftReason: formatStrPtr(p.LeftReason),
		Duration:   p.Duration,
	}
}

func recordingToDTO(r *meetingModel.MeetingRecording) dto.AdminMeetingRecordingDTO {
	out := dto.AdminMeetingRecordingDTO{
		ID:          r.ID,
		StartedBy:   r.StartedBy,
		Status:      r.Status,
		FileURL:     r.FileURL,
		SizeBytes:   r.SizeBytes,
		DurationSec: r.DurationSec,
		StartedAt:   r.StartedAt.Format(datetimeLayout),
		StoppedAt:   formatTimePtr(r.StoppedAt),
	}
	// 仅 failed 暴露 failure_reason；recording 状态下 FailureReason 字段被复用为本地路径，不应外泄
	if r.Status == meetingModel.MeetingRecordingStatusFailed {
		out.FailureReason = r.FailureReason
	}
	return out
}
