import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { ActorResolver, type MessagePrincipal } from '../identity/actor.resolver';
import { recordAudit } from '../observability/audit';

const REDACTED = '[redacted]';

export interface PlaceHoldInput {
  targetType: string;
  targetId: string;
  reason: string;
  expiresAt?: string;
}

/**
 * Data-subject rights (P9 DSR, KG-03). Right-to-erasure is doc-mandated as
 * tombstone/redact — never a hard delete: PII columns are stripped in place while
 * structure, IDs, and the audit trail are preserved ("source preservation"). An active
 * compliance hold blocks erasure until an authorised release. All ops are audited and
 * strictly tenant-scoped (the caller can only act within their own scope).
 */
@Injectable()
export class DsrService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly actors: ActorResolver,
  ) {}

  /** Erase the CALLER's own subject (self-service GDPR "erase me"). */
  async eraseSubject(principal: MessagePrincipal) {
    const scope = await this.actors.findScope(principal);
    if (!scope) return { status: 'NOTHING_TO_ERASE' as const };

    const handle = await this.prisma.iiosSourceHandle.findUnique({
      where: { scopeId_kind_externalId: { scopeId: scope.id, kind: 'PORTAL_USER', externalId: principal.userId } },
      include: { actors: true },
    });
    if (!handle) return { status: 'NOTHING_TO_ERASE' as const };
    if (handle.redactedAt) return { status: 'ALREADY_ERASED' as const, subjectId: handle.id };

    // A live compliance hold over this subject blocks erasure (legal hold overrides erasure).
    const hold = await this.prisma.iiosComplianceHold.findFirst({
      where: {
        scopeId: scope.id,
        targetType: 'data_subject',
        targetId: handle.id,
        status: 'ACTIVE',
        OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }],
      },
    });
    if (hold) {
      await recordAudit(this.prisma, { action: 'dsr.erasure.blocked', resourceType: 'data_subject', resourceId: handle.id, scopeId: scope.id });
      throw new ConflictException('erasure blocked by an active compliance hold');
    }

    const actorIds = handle.actors.map((a) => a.id);
    const interactionIds = (
      await this.prisma.iiosInteraction.findMany({ where: { actorId: { in: actorIds } }, select: { id: true } })
    ).map((i) => i.id);

    await this.prisma.$transaction([
      // Message content + raw provider payloads authored by the subject.
      this.prisma.iiosMessagePart.updateMany({ where: { interactionId: { in: interactionIds } }, data: { bodyText: REDACTED, contentRef: null } }),
      this.prisma.iiosInboundRawEvent.updateMany({ where: { interactionId: { in: interactionIds } }, data: { payload: { redacted: true } } }),
      // Support + inbox PII the subject authored/owns.
      this.prisma.iiosTicket.updateMany({ where: { requesterActorId: { in: actorIds } }, data: { subject: REDACTED } }),
      this.prisma.iiosCallbackRequest.updateMany({ where: { requesterActorId: { in: actorIds } }, data: { notes: null } }),
      this.prisma.iiosInboxItem.updateMany({ where: { ownerActorId: { in: actorIds } }, data: { title: REDACTED, summary: null } }),
      // Meeting transcript content the subject spoke; revoke their recording consent.
      this.prisma.iiosTranscriptSegment.updateMany({ where: { speakerActorId: { in: actorIds } }, data: { contentRef: REDACTED } }),
      this.prisma.iiosMeetingParticipant.updateMany({ where: { actorRefId: { in: actorIds } }, data: { recordingConsent: 'REVOKED' } }),
      // Identity: redact member-facing display, PRESERVE externalId (source preservation).
      this.prisma.iiosActorRef.updateMany({ where: { id: { in: actorIds } }, data: { displayName: REDACTED } }),
      this.prisma.iiosSourceHandle.update({ where: { id: handle.id }, data: { displayName: REDACTED, metadata: Prisma.DbNull, redactedAt: new Date() } }),
    ]);

    await recordAudit(this.prisma, { action: 'dsr.subject.erased', resourceType: 'data_subject', resourceId: handle.id, scopeId: scope.id });
    return { status: 'ERASED' as const, subjectId: handle.id, redactedInteractions: interactionIds.length };
  }

  async placeHold(principal: MessagePrincipal, input: PlaceHoldInput) {
    const scope = await this.actors.resolveScope(principal);
    let targetId = input.targetId;
    if (targetId === 'self') {
      const handle = await this.prisma.iiosSourceHandle.findUnique({
        where: { scopeId_kind_externalId: { scopeId: scope.id, kind: 'PORTAL_USER', externalId: principal.userId } },
      });
      if (!handle) throw new NotFoundException('no subject exists for the caller yet');
      targetId = handle.id;
    }
    const appliedBy = await this.actors.resolveActor(scope.id, principal);
    const hold = await this.prisma.iiosComplianceHold.create({
      data: {
        scopeId: scope.id,
        targetType: input.targetType,
        targetId,
        holdReason: input.reason,
        appliedByActorId: appliedBy.id,
        expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,
      },
    });
    await recordAudit(this.prisma, { action: 'dsr.hold.placed', resourceType: 'compliance_hold', resourceId: hold.id, scopeId: scope.id, actorRefId: appliedBy.id });
    return hold;
  }

  async releaseHold(principal: MessagePrincipal, id: string) {
    const hold = await this.prisma.iiosComplianceHold.findUnique({ where: { id } });
    if (!hold) throw new NotFoundException('compliance hold not found');
    const scope = await this.actors.assertOwns(principal, hold.scopeId); // KG-02 fence
    const released = await this.prisma.iiosComplianceHold.update({ where: { id }, data: { status: 'RELEASED', releasedAt: new Date() } });
    await recordAudit(this.prisma, { action: 'dsr.hold.released', resourceType: 'compliance_hold', resourceId: id, scopeId: scope.id });
    return released;
  }

  async listHolds(principal: MessagePrincipal) {
    const scope = await this.actors.findScope(principal);
    if (!scope) return [];
    return this.prisma.iiosComplianceHold.findMany({ where: { scopeId: scope.id, status: 'ACTIVE' }, orderBy: { createdAt: 'desc' } });
  }
}
