import { Injectable } from '@nestjs/common';
import { PrismaService } from '@modules/shared';
import type { ConversationEntity, ConversationParticipantEntity } from '../../domain/entities/conversation.entity';
import type {
  IConversationRepository,
  CreateConversationDto,
} from '../../domain/repositories/conversation.repository';
import type { ConversationStatus } from '../../domain/value-objects/conversation-status.vo';

@Injectable()
export class PrismaConversationRepository implements IConversationRepository {
  constructor(private readonly prisma: PrismaService) {}

  async create(dto: CreateConversationDto): Promise<ConversationEntity> {
    const record = await this.prisma.conversation.create({
      data: {
        listingId: dto.listingId ?? null,
        subject: dto.subject ?? null,
        participants: {
          create: dto.participantUserIds.map((userId) => ({ userId })),
        },
      },
      include: { participants: true },
    });
    return this.toEntity(record);
  }

  async findById(id: string): Promise<ConversationEntity | null> {
    const record = await this.prisma.conversation.findUnique({
      where: { id },
      include: { participants: true },
    });
    return record ? this.toEntity(record) : null;
  }

  async findByUserId(userId: string, limit = 20, offset = 0): Promise<ConversationEntity[]> {
    const records = await this.prisma.conversation.findMany({
      where: {
        participants: { some: { userId } },
      },
      include: { participants: true },
      orderBy: { lastMessageAt: { sort: 'desc', nulls: 'last' } },
      take: limit,
      skip: offset,
    });
    return records.map((r) => this.toEntity(r));
  }

  async findExistingBetweenUsers(
    userIds: string[],
    listingId?: string,
  ): Promise<ConversationEntity | null> {
    const record = await this.prisma.conversation.findFirst({
      where: {
        status: 'ACTIVE',
        ...(listingId ? { listingId } : {}),
        AND: userIds.map((userId) => ({
          participants: { some: { userId } },
        })),
        participants: { every: { userId: { in: userIds } } },
      },
      include: { participants: true },
    });
    return record ? this.toEntity(record) : null;
  }

  async updateStatus(id: string, status: ConversationStatus): Promise<void> {
    await this.prisma.conversation.update({
      where: { id },
      data: { status },
    });
  }

  async updateLastMessage(id: string, content: string, sentAt: Date): Promise<void> {
    await this.prisma.conversation.update({
      where: { id },
      data: {
        lastMessage: content.length > 200 ? content.slice(0, 200) + '...' : content,
        lastMessageAt: sentAt,
      },
    });
  }

  async incrementUnreadCount(conversationId: string, excludeUserId: string): Promise<void> {
    await this.prisma.conversationParticipant.updateMany({
      where: { conversationId, userId: { not: excludeUserId } },
      data: { unreadCount: { increment: 1 } },
    });
  }

  async resetUnreadCount(conversationId: string, userId: string): Promise<void> {
    await this.prisma.conversationParticipant.updateMany({
      where: { conversationId, userId },
      data: { unreadCount: 0, lastReadAt: new Date() },
    });
  }

  async countByUserId(userId: string): Promise<number> {
    return this.prisma.conversation.count({
      where: { participants: { some: { userId } } },
    });
  }

  private toEntity(record: {
    id: string;
    listingId: string | null;
    subject: string | null;
    status: string;
    lastMessage: string | null;
    lastMessageAt: Date | null;
    createdAt: Date;
    updatedAt: Date;
    participants: Array<{
      id: string;
      conversationId: string;
      userId: string;
      unreadCount: number;
      lastReadAt: Date | null;
      joinedAt: Date;
    }>;
  }): ConversationEntity {
    return {
      id: record.id,
      listingId: record.listingId,
      subject: record.subject,
      status: record.status as ConversationStatus,
      lastMessage: record.lastMessage,
      lastMessageAt: record.lastMessageAt,
      createdAt: record.createdAt,
      updatedAt: record.updatedAt,
      participants: record.participants.map((p): ConversationParticipantEntity => ({
        id: p.id,
        conversationId: p.conversationId,
        userId: p.userId,
        unreadCount: p.unreadCount,
        lastReadAt: p.lastReadAt,
        joinedAt: p.joinedAt,
      })),
    };
  }
}
