import mongoose from 'mongoose'
import { ChatMessage } from '../models/ChatMessage.js'
import { Meeting } from '../models/Meeting.js'
import { Participant } from '../models/Participant.js'
import { conflict, forbidden, notFound } from '../lib/errors.js'
import { createMeetingCode } from '../lib/meetingCode.js'
import type { Identity } from '../lib/tokens.js'

const isHost = (meeting: { hostId?: unknown; hostGuestId?: string | null }, identity: Identity) => {
  if (identity.kind === 'user' && meeting.hostId) {
    return String(meeting.hostId) === identity.sub
  }
  return identity.kind === 'guest' && meeting.hostGuestId === identity.sub
}

export const createMeeting = async (identity: Identity, title?: string) => {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const meetingCode = createMeetingCode()
    try {
      const meeting = await Meeting.create({
        meetingCode,
        title: title?.trim() || 'Instant meeting',
        hostId: identity.kind === 'user' ? identity.sub : null,
        hostGuestId: identity.kind === 'guest' ? identity.sub : null,
        status: 'live',
      })
      return meeting
    } catch (error) {
      if (error && typeof error === 'object' && 'code' in error && error.code === 11000) continue
      throw error
    }
  }
  throw conflict('Could not allocate a meeting code')
}

export const getMeetingByCode = async (code: string) => {
  const meeting = await Meeting.findOne({ meetingCode: code.toLowerCase() })
  if (!meeting) throw notFound('Meeting')
  return meeting
}

export const assertHost = async (code: string, identity: Identity) => {
  const meeting = await getMeetingByCode(code)
  if (!isHost(meeting, identity)) throw forbidden('Only the host can do that')
  return meeting
}

export const settingsOf = (meeting: {
  settings?: { waitingRoom?: boolean; allowChat?: boolean; allowScreenShare?: boolean } | null
}) => ({
  waitingRoom: meeting.settings?.waitingRoom ?? true,
  allowChat: meeting.settings?.allowChat ?? true,
  allowScreenShare: meeting.settings?.allowScreenShare ?? true,
})

export const toMeetingDto = (
  meeting: {
    meetingCode: string
    title: string
    status: string
    scheduledAt?: Date | null
    settings?: { waitingRoom?: boolean; allowChat?: boolean; allowScreenShare?: boolean } | null
    createdAt?: Date
    hostId?: unknown
    hostGuestId?: string | null
  },
  identity?: Identity,
) => ({
  meetingCode: meeting.meetingCode,
  title: meeting.title,
  status: meeting.status,
  scheduledAt: meeting.scheduledAt ?? null,
  settings: settingsOf(meeting),
  createdAt: meeting.createdAt ?? null,
  isHost: identity ? isHost(meeting, identity) : false,
})

export const recordParticipant = async (input: {
  meetingId: mongoose.Types.ObjectId
  identity: Identity
  displayName: string
  role: 'host' | 'participant'
  status: 'waiting' | 'admitted'
}) => {
  const filter =
    input.identity.kind === 'user'
      ? { meetingId: input.meetingId, userId: input.identity.sub }
      : { meetingId: input.meetingId, guestId: input.identity.sub }

  const now = new Date()
  const participant = await Participant.findOneAndUpdate(
    filter,
    {
      $set: {
        displayName: input.displayName,
        guestName: input.identity.kind === 'guest' ? input.displayName : null,
        role: input.role,
        status: input.status,
        leftAt: null,
        ...(input.status === 'admitted' ? { joinedAt: now } : {}),
      },
      $setOnInsert: {
        meetingId: input.meetingId,
        userId: input.identity.kind === 'user' ? input.identity.sub : null,
        guestId: input.identity.kind === 'guest' ? input.identity.sub : null,
      },
    },
    { upsert: true, returnDocument: 'after' },
  )
  return participant
}

export const listMessages = async (meetingId: mongoose.Types.ObjectId) => {
  const messages = await ChatMessage.find({ meetingId }).sort({ timestamp: 1 }).limit(200).lean()
  return messages.map((message) => ({
    id: message._id.toString(),
    senderId: message.senderId,
    senderName: message.senderName,
    message: message.message,
    timestamp: message.timestamp,
  }))
}

export const saveMessage = async (input: {
  meetingId: mongoose.Types.ObjectId
  senderId: string
  senderName: string
  message: string
}) => {
  const saved = await ChatMessage.create({ ...input, timestamp: new Date() })
  return {
    id: saved._id.toString(),
    senderId: saved.senderId,
    senderName: saved.senderName,
    message: saved.message,
    timestamp: saved.timestamp,
  }
}

export const markParticipantLeft = async (participantId: string, status: 'left' | 'removed' = 'left') => {
  if (!mongoose.isValidObjectId(participantId)) return
  await Participant.updateOne({ _id: participantId }, { status, leftAt: new Date() })
}
