import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { AuditService } from '../../common/audit/audit.service';
import { ProjectGovernanceRole, RbacService, normalizeProjectRole } from '../../common/rbac/rbac.service';
import { PrismaService } from '../../prisma/prisma.service';

export interface ProjectMemberMutationInput {
  actorId: string;
  projectId: string;
  userId: string;
  role?: ProjectGovernanceRole;
}

@Injectable()
export class ProjectMemberService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly rbacService: RbacService,
    private readonly auditService: AuditService,
  ) {}

  list(projectId: string) {
    return this.prisma.projectMember.findMany({
      where: { projectId: requireText(projectId, 'projectId') },
      include: { user: { select: { id: true, name: true, email: true } } },
      orderBy: [{ role: 'asc' }, { createdAt: 'asc' }],
    });
  }

  async add(input: ProjectMemberMutationInput) {
    const projectId = requireText(input.projectId, 'projectId');
    const userId = requireText(input.userId, 'userId');
    const actorId = requireText(input.actorId, 'actorId');
    const role = normalizeRequiredRole(input.role ?? 'member');
    await this.rbacService.assertProjectRole({ actorId, projectId, allowedRoles: ['admin'] });

    const created = await this.prisma.projectMember.create({
      data: { projectId, userId, role },
    });
    await this.auditService.record({
      actorId,
      action: 'project_member.created',
      resourceType: 'project_member',
      resourceId: created.id,
      projectId,
      after: created,
    });
    return created;
  }

  async updateRole(input: Required<ProjectMemberMutationInput>) {
    const projectId = requireText(input.projectId, 'projectId');
    const userId = requireText(input.userId, 'userId');
    const actorId = requireText(input.actorId, 'actorId');
    const role = normalizeRequiredRole(input.role);
    await this.rbacService.assertProjectRole({ actorId, projectId, allowedRoles: ['admin'] });

    const existing = await this.findMembership(projectId, userId);
    if (existing.role === 'owner' && role !== 'owner') {
      await this.assertOwnerWillRemain(projectId);
    }

    const updated = await this.prisma.projectMember.update({
      where: { projectId_userId: { projectId, userId } },
      data: { role },
    });
    await this.auditService.record({
      actorId,
      action: 'project_member.role_changed',
      resourceType: 'project_member',
      resourceId: updated.id,
      projectId,
      before: existing,
      after: updated,
    });
    return updated;
  }

  async remove(input: Omit<ProjectMemberMutationInput, 'role'>) {
    const projectId = requireText(input.projectId, 'projectId');
    const userId = requireText(input.userId, 'userId');
    const actorId = requireText(input.actorId, 'actorId');
    await this.rbacService.assertProjectRole({ actorId, projectId, allowedRoles: ['admin'] });

    const existing = await this.findMembership(projectId, userId);
    if (existing.role === 'owner') {
      await this.assertOwnerWillRemain(projectId);
    }

    const removed = await this.prisma.projectMember.delete({
      where: { projectId_userId: { projectId, userId } },
    });
    await this.auditService.record({
      actorId,
      action: 'project_member.deleted',
      resourceType: 'project_member',
      resourceId: removed.id,
      projectId,
      before: existing,
    });
    return removed;
  }

  private async findMembership(projectId: string, userId: string) {
    const membership = await this.prisma.projectMember.findUnique({
      where: { projectId_userId: { projectId, userId } },
    });
    if (!membership) throw new NotFoundException('Project member not found');
    return membership;
  }

  private async assertOwnerWillRemain(projectId: string) {
    const ownerCount = await this.prisma.projectMember.count({ where: { projectId, role: 'owner' } });
    if (ownerCount <= 1) {
      throw new BadRequestException('Cannot remove or demote the last project owner');
    }
  }
}

function normalizeRequiredRole(role: string | undefined): ProjectGovernanceRole {
  const normalized = normalizeProjectRole(role);
  if (!normalized) throw new BadRequestException(`Unsupported project member role: ${role}`);
  return normalized;
}

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