// Package service 提供 meeting 模块的业务逻辑
package service

import (
	"context"
	"encoding/json"

	authModel "github.com/echochat/backend/app/auth/model"
	notifyService "github.com/echochat/backend/app/notify/service"
)

// NotifyPusher 通知推送接口
// 由 notify.service.NotifyService 隐式实现，供 meeting 模块注入使用
// 会议邀请、主持人变更、踢出会议等事件通过此接口落地通知中心
type NotifyPusher interface {
	Push(ctx context.Context, payload *notifyService.PushPayload)
	PushBatch(ctx context.Context, payloads []*notifyService.PushPayload)
}

// UserInfoResolver 查询用户昵称 / 头像的接口
// 由 contact.FriendshipDAO 隐式实现（已有 GetUsersByIDs 方法）
// 用于拉取会议邀请中主持人信息、成员列表头像等
type UserInfoResolver interface {
	GetUsersByIDs(ctx context.Context, userIDs []int64) ([]authModel.User, error)
}

// OnlineChecker 查询用户在线状态的接口
// 由 ws.OnlineService 隐式实现（签名对齐现有实现：不返回 error，查询失败按离线处理）
// 用于会议邀请时判断收件人是否在线（离线则降级为仅通知入库，上线时通过未读补偿推送）
type OnlineChecker interface {
	IsOnline(ctx context.Context, userID int64) bool
}

// ====== MediaOrchestrator：Go → Node media-server 统一封装（设计 §6.6）======

// TransportInfo Node 端创建的 Transport 元信息
// 字段与 mediasoup-client 的 Device.createSendTransport / createRecvTransport 入参兼容
type TransportInfo struct {
	ID             string          `json:"id"`              // Transport ID
	IceParameters  json.RawMessage `json:"iceParameters"`   // mediasoup ICE 参数
	IceCandidates  json.RawMessage `json:"iceCandidates"`   // ICE 候选列表
	DtlsParameters json.RawMessage `json:"dtlsParameters"`  // DTLS 指纹参数
	SctpParameters json.RawMessage `json:"sctpParameters,omitempty"`
}

// ConsumerInfo Node 端创建的 Consumer 元信息
type ConsumerInfo struct {
	ID             string          `json:"id"`
	ProducerID     string          `json:"producerId"`
	Kind           string          `json:"kind"`           // "audio" | "video"
	RtpParameters  json.RawMessage `json:"rtpParameters"`
	Type           string          `json:"type,omitempty"` // "simple" | "simulcast" | "svc"
	ProducerPaused bool            `json:"producerPaused,omitempty"`
}

// CreateTransportReq 创建 Transport 请求
type CreateTransportReq struct {
	RoomCode  string `json:"roomCode"`
	UserID    int64  `json:"userId"`
	Direction string `json:"direction"` // "send" | "recv"
}

// CreateProducerReq 创建 Producer 请求
type CreateProducerReq struct {
	RoomCode      string          `json:"roomCode"`
	UserID        int64           `json:"userId"`
	TransportID   string          `json:"transportId"`
	Kind          string          `json:"kind"`          // "audio" | "video"
	RtpParameters json.RawMessage `json:"rtpParameters"` // 直接转发给 Node，由其做结构校验
	// AppData 客户端自定义元信息，例如 {"screen": true} 表示该 Producer 是屏幕共享流
	// Phase 3 屏幕共享引入：与摄像头/麦克风 video Producer 同走 sendTransport，
	// 通过 appData.screen 区分，便于服务端识别并触发 meeting.screen.* 广播
	AppData json.RawMessage `json:"appData,omitempty"`
}

// CreateConsumerReq 创建 Consumer 请求
type CreateConsumerReq struct {
	RoomCode        string          `json:"roomCode"`
	UserID          int64           `json:"userId"`
	TransportID     string          `json:"transportId"`
	ProducerID      string          `json:"producerId"`
	RtpCapabilities json.RawMessage `json:"rtpCapabilities"`
}

// ====== 会议录制 RPC（Phase B 引入）======

// StartRecordingReq 启动录制请求
//
// ProducerIDs 指定要录的若干 Producer，由业务层决定（Phase B1 通常是 host 的
// audio + video 两个；Phase B3 扩展为屏幕共享 + 多人音频后会更多）
type StartRecordingReq struct {
	RouterID    string   `json:"routerId"`
	RoomCode    string   `json:"roomCode"`
	ProducerIDs []string `json:"producerIds"`
}

// StartRecordingResp media-server 启动成功后的响应
type StartRecordingResp struct {
	RecordingID string `json:"recordingId"`
	OutputPath  string `json:"outputPath"` // media-server 本地 mp4 路径，用于停止后回拉上传
	Tracks      []struct {
		ProducerID string `json:"producerId"`
		Kind       string `json:"kind"`
	} `json:"tracks"`
}

// StopRecordingResp media-server 停止录制后返回的元数据
type StopRecordingResp struct {
	RecordingID   string `json:"recordingId"`
	OutputPath    string `json:"outputPath"`
	SizeBytes     int64  `json:"sizeBytes"`
	DurationMS    int64  `json:"durationMs"`
	ExitCode      *int   `json:"exitCode,omitempty"`     // ffmpeg 退出码；nil = 进程未启动到退出阶段
	FailureReason string `json:"failureReason,omitempty"` // 非空表示录制最终标记为 failed
}

// MediaOrchestrator 媒体服务器编排接口（设计 §6.6 NodeClient）
// Task 7 (2026-04-21) 起默认实现为 HTTPMediaOrchestrator（通过 X-Internal-Token
// 调用 Node media-server 的 /internal/v1/* REST API）。
// NoopMediaOrchestrator 仅保留用于本地调试（无 Node 服务时可临时切换）：
//   - CreateRouter 返回 "noop-router-{code}"；其他方法返回可解析的占位数据
//   - 所有方法均幂等：重复调用不报错，符合 WS 信令重试语义
type MediaOrchestrator interface {
	// CreateRouter 为会议房间创建 mediasoup Router
	// Task 8 起实现需自带 sync.Map 幂等缓存：roomCode 已有 Router 时直接返回缓存值
	CreateRouter(ctx context.Context, roomCode string) (routerID string, err error)
	// CloseRouter 关闭房间对应 Router 及其下所有资源（幂等）
	CloseRouter(ctx context.Context, roomCode string) error
	// ResolveRouterID 从本地缓存查询 roomCode 对应的 routerID（不触发 HTTP 调用）
	// Task 8 引入：用于 JoinRoom 等"仅需复用"的场景，避免重复调 CreateRouter
	// 返回 (id, true) 命中；(_, false) 缺失（通常说明房间未创建 Router 或服务重启后未 reschedule）
	ResolveRouterID(roomCode string) (string, bool)
	// ResolveRouterInfo 读取 roomCode 对应的 Router 完整信息（routerID + rtpCapabilities）
	// Task 9 引入：前端 mediasoup-client Device.load 必须 rtpCapabilities，JoinRoom 响应由此填充
	// 返回 (id, caps, true) 命中；(_, _, false) 缺失
	ResolveRouterInfo(roomCode string) (string, json.RawMessage, bool)

	// CreateTransport 为用户创建 send/recv WebRTC Transport
	CreateTransport(ctx context.Context, req *CreateTransportReq) (*TransportInfo, error)
	// ConnectTransport Transport DTLS 握手（幂等：重复 connect 对已连接 transport 视为成功）
	ConnectTransport(ctx context.Context, transportID string, dtlsParameters json.RawMessage) error
	// CloseTransport 主动关闭指定 Transport（Task 16 P2-1 引入）
	// 场景：用户离会 / WS 断连时 cleanupUserResources 精确清理，避免等待 Router 级联
	// 语义：404（Transport 已关闭/不存在）返回 ErrMediaResourceNotFound，上层可视为"已清理"幂等成功
	CloseTransport(ctx context.Context, transportID string) error

	// CreateProducer 在指定 send Transport 上创建 Producer
	CreateProducer(ctx context.Context, req *CreateProducerReq) (producerID string, err error)
	// CloseProducer 关闭指定 Producer（幂等）
	CloseProducer(ctx context.Context, producerID string) error

	// CreateConsumer 在指定 recv Transport 上创建 Consumer（订阅远端 Producer）
	// Node 端创建的 Consumer 默认 paused=true，需在前端 track 就绪后调用 ResumeConsumer 才会开始推流
	CreateConsumer(ctx context.Context, req *CreateConsumerReq) (*ConsumerInfo, error)
	// ResumeConsumer 恢复 Consumer 开始接收 RTP（Task 9 引入）
	// 前端 recv Transport 与 track 挂载完成后由 meeting.consume.resume WS 事件触发
	// Node 未找到对应 Consumer 返回 ErrMediaResourceNotFound，调用方可按"已失效"处理
	ResumeConsumer(ctx context.Context, consumerID string) error
	// CloseConsumer 关闭指定 Consumer（幂等）
	CloseConsumer(ctx context.Context, consumerID string) error

	// ====== 录制（Phase B 引入）======

	// StartRecording 在 media-server 上启动一份会议录制
	// 失败语义：404 RouterNotFound / 409 producerId 不可消费等返回 ErrMediaServerError
	// 成功返回 recordingId（hex32）+ media-server 本地 mp4 输出路径
	StartRecording(ctx context.Context, req *StartRecordingReq) (*StartRecordingResp, error)
	// StopRecording 停止录制
	// 幂等：媒体侧已结束（404）返回 ErrMediaResourceNotFound，调用方一般视为"已停止"
	StopRecording(ctx context.Context, recordingID string) (*StopRecordingResp, error)
}

// NoopMediaOrchestrator 本地调试占位实现（Task 7 后默认不再使用）
// 返回伪造的 ID 与固定占位数据（JSON：空对象 / 空数组），所有操作仅写日志不调用 Node
// Task 7 已将全局 wire 绑定切换为 HTTPMediaOrchestrator，此类型仅作为无 Node 服务时
// 的临时回退（需手动修改 provider.go 的 wire.Bind 才会生效）
type NoopMediaOrchestrator struct{}

// NewNoopMediaOrchestrator 构造占位的 MediaOrchestrator
func NewNoopMediaOrchestrator() *NoopMediaOrchestrator {
	return &NoopMediaOrchestrator{}
}

// CreateRouter 返回以 "noop-router-" 为前缀的伪造 RouterID
func (n *NoopMediaOrchestrator) CreateRouter(_ context.Context, roomCode string) (string, error) {
	return "noop-router-" + roomCode, nil
}

// CloseRouter 占位实现：直接返回 nil
func (n *NoopMediaOrchestrator) CloseRouter(_ context.Context, _ string) error {
	return nil
}

// ResolveRouterID 占位实现：始终命中，返回 noop-router-{code}
func (n *NoopMediaOrchestrator) ResolveRouterID(roomCode string) (string, bool) {
	return "noop-router-" + roomCode, true
}

// ResolveRouterInfo 占位：返回空 rtpCapabilities 对象（保证前端 mediasoup-client 能通过 JSON 解析，但 Device.load 会失败；适合本地无 Node 调试）
func (n *NoopMediaOrchestrator) ResolveRouterInfo(roomCode string) (string, json.RawMessage, bool) {
	return "noop-router-" + roomCode, json.RawMessage(`{}`), true
}

// CreateTransport 占位：返回以 "noop-transport-" 为前缀的伪造 ID，带最小合法 JSON 结构
func (n *NoopMediaOrchestrator) CreateTransport(_ context.Context, req *CreateTransportReq) (*TransportInfo, error) {
	return &TransportInfo{
		ID:             "noop-transport-" + req.Direction,
		IceParameters:  json.RawMessage(`{}`),
		IceCandidates:  json.RawMessage(`[]`),
		DtlsParameters: json.RawMessage(`{}`),
	}, nil
}

// ConnectTransport 占位：直接返回 nil
func (n *NoopMediaOrchestrator) ConnectTransport(_ context.Context, _ string, _ json.RawMessage) error {
	return nil
}

// CloseTransport 占位：直接返回 nil（Task 16 P2-1 引入）
func (n *NoopMediaOrchestrator) CloseTransport(_ context.Context, _ string) error {
	return nil
}

// CreateProducer 占位：返回伪造 ID
func (n *NoopMediaOrchestrator) CreateProducer(_ context.Context, req *CreateProducerReq) (string, error) {
	return "noop-producer-" + req.Kind, nil
}

// CloseProducer 占位：直接返回 nil
func (n *NoopMediaOrchestrator) CloseProducer(_ context.Context, _ string) error {
	return nil
}

// CreateConsumer 占位：返回伪造的 ConsumerInfo（含目标 producerID 回填）
func (n *NoopMediaOrchestrator) CreateConsumer(_ context.Context, req *CreateConsumerReq) (*ConsumerInfo, error) {
	return &ConsumerInfo{
		ID:            "noop-consumer-" + req.ProducerID,
		ProducerID:    req.ProducerID,
		Kind:          "video",
		RtpParameters: json.RawMessage(`{}`),
		Type:          "simple",
	}, nil
}

// ResumeConsumer 占位：直接返回 nil
func (n *NoopMediaOrchestrator) ResumeConsumer(_ context.Context, _ string) error {
	return nil
}

// CloseConsumer 占位：直接返回 nil
func (n *NoopMediaOrchestrator) CloseConsumer(_ context.Context, _ string) error {
	return nil
}

// StartRecording 占位：返回伪造 recordingId 与空 outputPath
func (n *NoopMediaOrchestrator) StartRecording(_ context.Context, req *StartRecordingReq) (*StartRecordingResp, error) {
	return &StartRecordingResp{
		RecordingID: "noop-recording-" + req.RoomCode,
		OutputPath:  "",
	}, nil
}

// StopRecording 占位：返回零大小元数据
func (n *NoopMediaOrchestrator) StopRecording(_ context.Context, recordingID string) (*StopRecordingResp, error) {
	return &StopRecordingResp{
		RecordingID: recordingID,
	}, nil
}
