import { Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import type {
  PortalAsset,
  PortalRequestCreate,
  PortalRequestCreated,
  PortalRequestStatus,
  PortalStep,
} from '@siop/shared';
import { randomBytes } from 'node:crypto';
import { PrismaService } from '../prisma/prisma.service';

const requestInclude = { workOrder: true } satisfies Prisma.RequestInclude;
type Row = Prisma.RequestGetPayload<{ include: typeof requestInclude }>;

@Injectable()
export class PortalService {
  constructor(private readonly prisma: PrismaService) {}

  /** Le QR encode la référence (imprimée sur l'étiquette). */
  async resolveAsset(reference: string): Promise<PortalAsset> {
    const asset = await this.prisma.asset.findUnique({
      where: { reference },
      include: { location: { include: { parent: true } } },
    });
    if (!asset) throw new NotFoundException('Référence inconnue');
    return {
      reference: asset.reference,
      siteName: asset.location.parent?.name ?? asset.location.name,
      locationName: asset.location.name,
    };
  }

  async createRequest(dto: PortalRequestCreate): Promise<PortalRequestCreated> {
    const asset = await this.prisma.asset.findUnique({
      where: { reference: dto.assetReference },
    });
    if (!asset) throw new NotFoundException('Référence inconnue');
    for (let essai = 0; ; essai++) {
      try {
        const created = await this.prisma.request.create({
          data: {
            reference: await this.nextReference(),
            description: dto.description,
            isPersonTrapped: dto.isPersonTrapped ?? false,
            assetId: asset.id,
            requesterName: dto.requesterName || 'Portail (QR cabine)',
            publicToken: randomBytes(24).toString('base64url'),
          },
          include: requestInclude,
        });
        return { ...this.toStatus(created), publicToken: created.publicToken! };
      } catch (e) {
        if (
          e instanceof Prisma.PrismaClientKnownRequestError &&
          e.code === 'P2002' &&
          essai < 3
        ) {
          continue; // collision de référence — on retente
        }
        throw e;
      }
    }
  }

  /** Suivi par jeton opaque — pas de jeton, pas de lecture (aucune fuite). */
  async status(reference: string, token: string): Promise<PortalRequestStatus> {
    const request = await this.prisma.request.findUnique({
      where: { reference },
      include: requestInclude,
    });
    if (!request || !request.publicToken || request.publicToken !== token) {
      throw new NotFoundException('Signalement inconnu ou jeton invalide');
    }
    return this.toStatus(request);
  }

  private async nextReference(): Promise<string> {
    const [{ nextval }] = await this.prisma.$queryRaw<[{ nextval: bigint }]>`
      SELECT nextval('request_ref_seq')`;
    return `DEM-${new Date().getFullYear()}-${String(nextval).padStart(4, '0')}`;
  }

  private toStatus(row: Row): PortalRequestStatus {
    const step: PortalStep =
      row.workOrder?.status === 'DONE'
        ? 'RESOLVED'
        : row.workOrder && row.workOrder.startedAt
          ? 'IN_PROGRESS'
          : 'RECEIVED';
    return {
      reference: row.reference,
      description: row.description,
      isPersonTrapped: row.isPersonTrapped,
      status: row.status,
      step,
      rejectionReason: row.rejectionReason,
      createdAt: row.createdAt.toISOString(),
    };
  }
}
