import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import * as crypto from 'crypto';
import { DocumentCategory, SignatureStatus } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { AuthenticatedUser } from '../../common/decorators/current-user.decorator';
import { StubSignatureProvider } from './providers/signature-provider.interface';

interface UploadDocumentInput {
  complexId: string;
  title: string;
  category?: DocumentCategory;
  folder?: string;
  isPublic?: boolean;
  filename: string;
  url: string;           // pre-uploaded to object storage (S3/MinIO); front passes URL
  mimeType?: string;
  sizeBytes?: number;
  contentBase64?: string; // optional inline content for dev hashing
}

@Injectable()
export class DocumentsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly signatures: StubSignatureProvider,
  ) {}

  async upload(user: AuthenticatedUser, dto: UploadDocumentInput) {
    if (!user.tenantId) throw new BadRequestException('Tenant required');
    const complex = await this.prisma.complex.findFirst({
      where: { id: dto.complexId, tenantId: user.tenantId },
    });
    if (!complex) throw new NotFoundException('Complex not found');

    const hash = dto.contentBase64
      ? crypto.createHash('sha256').update(dto.contentBase64).digest('hex')
      : null;

    return this.prisma.$transaction(async (tx) => {
      const doc = await tx.document.create({
        data: {
          complexId: dto.complexId,
          uploaderId: user.userId,
          title: dto.title,
          category: dto.category ?? DocumentCategory.AUTRE,
          folder: dto.folder,
          isPublic: dto.isPublic ?? false,
        },
      });
      const version = await tx.documentVersion.create({
        data: {
          documentId: doc.id,
          version: 1,
          url: dto.url,
          filename: dto.filename,
          mimeType: dto.mimeType,
          sizeBytes: dto.sizeBytes,
          hash,
        },
      });
      return tx.document.update({
        where: { id: doc.id },
        data: { latestVersionId: version.id },
        include: { versions: true },
      });
    });
  }

  async addVersion(user: AuthenticatedUser, documentId: string, dto: Omit<UploadDocumentInput, 'complexId' | 'title' | 'category' | 'folder' | 'isPublic'>) {
    const doc = await this.findOne(user, documentId);
    const last = await this.prisma.documentVersion.findFirst({
      where: { documentId },
      orderBy: { version: 'desc' },
    });
    const version = await this.prisma.documentVersion.create({
      data: {
        documentId,
        version: (last?.version ?? 0) + 1,
        url: dto.url,
        filename: dto.filename,
        mimeType: dto.mimeType,
        sizeBytes: dto.sizeBytes,
        hash: dto.contentBase64
          ? crypto.createHash('sha256').update(dto.contentBase64).digest('hex')
          : null,
      },
    });
    return this.prisma.document.update({
      where: { id: doc.id },
      data: { latestVersionId: version.id },
      include: { versions: { orderBy: { version: 'desc' } } },
    });
  }

  async list(user: AuthenticatedUser, complexId?: string, category?: DocumentCategory) {
    if (!user.tenantId && !user.isSuperAdmin) return [];
    const isResidentOnly = !user.roles.some((r) =>
      ['SUPERADMIN', 'SYNDIC', 'GARDIEN', 'PRESTATAIRE'].includes(r.code),
    );
    return this.prisma.document.findMany({
      where: {
        ...(complexId && { complexId }),
        ...(category && { category }),
        ...(user.tenantId && { complex: { tenantId: user.tenantId } }),
        ...(isResidentOnly && { isPublic: true }),
      },
      orderBy: { updatedAt: 'desc' },
      include: { versions: { orderBy: { version: 'desc' }, take: 1 } },
    });
  }

  async findOne(user: AuthenticatedUser, id: string) {
    const doc = await this.prisma.document.findUnique({
      where: { id },
      include: {
        complex: true,
        versions: { orderBy: { version: 'desc' } },
        signatureRequests: {
          include: { signer: { select: { id: true, email: true } } },
        },
      },
    });
    if (!doc) throw new NotFoundException('Document not found');
    if (!user.isSuperAdmin && doc.complex.tenantId !== user.tenantId) {
      throw new ForbiddenException();
    }
    const isResidentOnly = !user.roles.some((r) =>
      ['SUPERADMIN', 'SYNDIC', 'GARDIEN', 'PRESTATAIRE'].includes(r.code),
    );
    if (isResidentOnly && !doc.isPublic) throw new ForbiddenException();
    return doc;
  }

  // ---------- Signatures ----------

  async requestSignature(
    user: AuthenticatedUser,
    documentId: string,
    signerId: string,
  ) {
    const doc = await this.findOne(user, documentId);
    const signer = await this.prisma.user.findUnique({ where: { id: signerId } });
    if (!signer) throw new NotFoundException('Signer not found');
    const latest = doc.versions[0];
    if (!latest) throw new BadRequestException('Document has no version');

    const result = await this.signatures.createSigningRequest({
      documentUrl: latest.url,
      documentTitle: doc.title,
      signerEmail: signer.email,
      signerName: signer.firstName ?? undefined,
    });

    return this.prisma.signatureRequest.create({
      data: {
        documentId,
        signerId,
        provider: this.signatures.code,
        providerRef: result.providerRef,
        signingUrl: result.signingUrl,
        expiresAt: result.expiresAt,
        status: SignatureStatus.PENDING,
      },
    });
  }

  async completeStubSignature(providerRef: string) {
    if (process.env.ENABLE_STUB_PROVIDERS !== 'true') {
      throw new BadRequestException('Stub signature endpoint is disabled');
    }
    const req = await this.prisma.signatureRequest.findFirst({ where: { providerRef } });
    if (!req) return null;
    if (req.status === SignatureStatus.PENDING) {
      await this.prisma.signatureRequest.update({
        where: { id: req.id },
        data: { status: SignatureStatus.SIGNED, signedAt: new Date() },
      });
    }
    return req;
  }

  async handleSignatureCallback(rawBody: string, _headers: Record<string, string>) {
    const parsed = await this.signatures.parseCallback(rawBody);
    const req = await this.prisma.signatureRequest.findFirst({
      where: { providerRef: parsed.providerRef },
    });
    if (!req) return { ok: false };
    return this.prisma.signatureRequest.update({
      where: { id: req.id },
      data: {
        status: parsed.status,
        signedAt: parsed.status === SignatureStatus.SIGNED ? new Date() : null,
        declinedAt: parsed.status === SignatureStatus.DECLINED ? new Date() : null,
      },
    });
  }

  async remove(user: AuthenticatedUser, id: string) {
    const doc = await this.findOne(user, id);
    if (!doc) throw new NotFoundException();
    await this.prisma.document.delete({ where: { id } });
    return { deleted: true };
  }
}
