import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { AuditService } from '../../common/audit/audit.service';
import { PrismaService } from '../../prisma/prisma.service';
import { NotificationService } from '../notification/notification.service';

export const COMMENT_ENTITY_TYPES = ['dev_task', 'test_case', 'bug', 'requirement', 'version_plan'] as const;
export type CommentEntityType = (typeof COMMENT_ENTITY_TYPES)[number];

export interface CommentCreateInput {
  actorId: string;
  entityType: CommentEntityType;
  entityId: string;
  entityVersionId?: string | null;
  productId?: string | null;
  projectId?: string | null;
  versionId?: string | null;
  content: string;
  mentionMemberIds?: string[];
}

@Injectable()
export class CommentService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly notificationService: NotificationService,
    private readonly auditService: AuditService,
  ) {}

  list(entityType: CommentEntityType, entityId: string) {
    assertCommentEntityType(entityType);
    return this.prisma.comment.findMany({
      where: { entityType, entityId: requireText(entityId, 'entityId'), deletedAt: null },
      orderBy: { createdAt: 'asc' },
    });
  }

  async create(input: CommentCreateInput) {
    assertCommentEntityType(input.entityType);
    const actorId = requireText(input.actorId, 'actorId');
    const content = requireText(input.content, 'content');
    const mentionedMemberIds = await this.resolveMentionMemberIds(content, input.projectId, input.mentionMemberIds);
    const comment = await this.prisma.comment.create({
      data: {
        entityType: input.entityType,
        entityId: requireText(input.entityId, 'entityId'),
        entityVersionId: input.entityVersionId ?? null,
        productId: input.productId ?? null,
        projectId: input.projectId ?? null,
        versionId: input.versionId ?? null,
        authorId: actorId,
        content,
        mentionedMemberIds,
      },
    });

    await this.notificationService.createMany(mentionedMemberIds
      .filter((recipientId) => recipientId !== actorId)
      .map((recipientId) => ({
        recipientId,
        actorId,
        type: 'mention',
        title: '你被提及了',
        body: content,
        resourceType: 'comment',
        resourceId: comment.id,
        resourceVersionId: input.entityVersionId ?? null,
        productId: input.productId ?? null,
        projectId: input.projectId ?? null,
        versionId: input.versionId ?? null,
        metadata: {
          entityType: input.entityType,
          entityId: input.entityId,
        },
      })));

    await this.auditService.record({
      actorId,
      action: 'comment.created',
      resourceType: 'comment',
      resourceId: comment.id,
      productId: input.productId ?? null,
      projectId: input.projectId ?? null,
      versionId: input.versionId ?? null,
      after: comment,
    });

    return comment;
  }

  async remove(id: string, actorId: string) {
    const comment = await this.prisma.comment.findUnique({ where: { id: requireText(id, 'id') } });
    if (!comment || comment.deletedAt) throw new NotFoundException('Comment not found');

    const removed = await this.prisma.comment.update({
      where: { id: comment.id },
      data: { deletedAt: new Date() },
    });
    await this.auditService.record({
      actorId: requireText(actorId, 'actorId'),
      action: 'comment.deleted',
      resourceType: 'comment',
      resourceId: comment.id,
      productId: comment.productId,
      projectId: comment.projectId,
      versionId: comment.versionId,
      before: comment,
      after: removed,
    });
    return removed;
  }

  private async resolveMentionMemberIds(content: string, projectId?: string | null, explicitIds: string[] = []): Promise<string[]> {
    const ids = new Set<string>();
    const mentionNames = extractMentionNames(content);
    if (mentionNames.length > 0 && projectId?.trim()) {
      const projectMembers = await this.prisma.projectMember.findMany({
        where: { projectId: projectId.trim() },
        include: { user: { select: { id: true, name: true } } },
      });
      const wanted = new Set(mentionNames.map(normalizeMentionName));
      for (const member of projectMembers as Array<{ userId: string; user?: { id?: string; name?: string } }>) {
        const name = normalizeMentionName(member.user?.name);
        if (name && wanted.has(name)) ids.add(member.user?.id ?? member.userId);
      }
    }
    for (const explicitId of explicitIds) {
      const id = explicitId.trim();
      if (id) ids.add(id);
    }
    return Array.from(ids);
  }
}

export function extractMentionNames(content: string): string[] {
  const names: string[] = [];
  const pattern = /@([\p{L}\p{N}_\-.]+)/gu;
  for (const match of content.matchAll(pattern)) {
    if (match[1]) names.push(match[1]);
  }
  return names;
}

function assertCommentEntityType(entityType: string): asserts entityType is CommentEntityType {
  if (!COMMENT_ENTITY_TYPES.includes(entityType as CommentEntityType)) {
    throw new BadRequestException(`Unsupported comment entity type: ${entityType}`);
  }
}

function normalizeMentionName(name?: string | null): string {
  return name?.trim().toLowerCase() ?? '';
}

function requireText(value: string | undefined | null, field: string): string {
  const normalized = value?.trim();
  if (!normalized) throw new BadRequestException(`${field} is required`);
  return normalized;
}
