import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PkiService } from './pki.service';
import { ProvisionMqttCertDto } from './dto/certificates.dto';

const CERT_SELECT = {
  id: true,
  serialNumber: true,
  commonName: true,
  fingerprint: true,
  issuedAt: true,
  expiresAt: true,
};

@Injectable()
export class CertificatesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly pki: PkiService,
  ) {}

  getCaStatus() {
    return this.pki.caStatus();
  }

  async initCa() {
    await this.pki.initCa();
    return this.pki.caStatus();
  }

  async provisionMqttCert(dto: ProvisionMqttCertDto) {
    await this.pki.provisionServerCert(dto.hostname);
    return { ok: true };
  }

  async downloadCaCert() {
    return this.pki.caCertPem();
  }

  async issueDeviceCertificate(orgId: string, deviceId: string) {
    const device = await this.getDevice(orgId, deviceId);
    if (!device.serialNumber) {
      throw new BadRequestException('Device needs a serial number before a certificate can be issued');
    }
    const existing = await this.prisma.deviceCertificate.findUnique({ where: { deviceId } });
    if (existing) {
      throw new ConflictException('Device already has a certificate — revoke it before issuing a new one');
    }

    const issued = await this.pki.issueDeviceCert(device.serialNumber);
    const record = await this.prisma.deviceCertificate.create({
      data: {
        deviceId,
        serialNumber: device.serialNumber,
        commonName: device.serialNumber.toUpperCase(),
        certificatePem: issued.certificatePem,
        privateKeyPem: issued.privateKeyPem,
        fingerprint: issued.fingerprint,
        expiresAt: issued.expiresAt,
      },
      select: CERT_SELECT,
    });
    return record;
  }

  async getDeviceCertificate(orgId: string, deviceId: string) {
    await this.getDevice(orgId, deviceId);
    const cert = await this.prisma.deviceCertificate.findUnique({ where: { deviceId }, select: CERT_SELECT });
    if (!cert) {
      throw new NotFoundException('Device has no certificate');
    }
    return cert;
  }

  async downloadDeviceCert(orgId: string, deviceId: string): Promise<string> {
    await this.getDevice(orgId, deviceId);
    const cert = await this.prisma.deviceCertificate.findUnique({ where: { deviceId } });
    if (!cert) {
      throw new NotFoundException('Device has no certificate');
    }
    return cert.certificatePem;
  }

  async downloadDeviceKey(orgId: string, deviceId: string): Promise<string> {
    await this.getDevice(orgId, deviceId);
    const cert = await this.prisma.deviceCertificate.findUnique({ where: { deviceId } });
    if (!cert) {
      throw new NotFoundException('Device has no certificate');
    }
    return cert.privateKeyPem;
  }

  async revokeDeviceCertificate(orgId: string, deviceId: string) {
    await this.getDevice(orgId, deviceId);
    const cert = await this.prisma.deviceCertificate.findUnique({ where: { deviceId } });
    if (!cert) {
      throw new NotFoundException('Device has no certificate');
    }
    // Hard delete: this is DB bookkeeping only, not broker-enforced (no
    // CRL/OCSP) — the device's existing cert still authenticates until it
    // expires. See README known gaps.
    await this.prisma.deviceCertificate.delete({ where: { deviceId } });
    return { ok: true };
  }

  private async getDevice(orgId: string, deviceId: string) {
    const device = await this.prisma.device.findFirst({ where: { id: deviceId, orgId } });
    if (!device) {
      throw new NotFoundException('Device not found');
    }
    return device;
  }
}
