import { Injectable, NotFoundException } from "@nestjs/common";
import { WebhookEndpoint } from "@prisma/client";
import { randomBytes } from "node:crypto";
import { EncryptionService, EncryptedPayload } from "../common/crypto/encryption.service";
import { newId } from "../common/ids/id.util";
import { PrismaService } from "../prisma/prisma.service";
import type { CreateWebhookEndpointDto } from "./dto/create-webhook-endpoint.dto";
import type { UpdateWebhookEndpointDto } from "./dto/update-webhook-endpoint.dto";

@Injectable()
export class WebhookEndpointService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly encryption: EncryptionService,
  ) {}

  async create(merchantId: string, dto: CreateWebhookEndpointDto) {
    const secret = this.generateSecret();
    const encrypted = this.encryption.encrypt(secret);

    const row = await this.prisma.webhookEndpoint.create({
      data: {
        id: newId("webhookEndpoint"),
        merchantId,
        url: dto.url,
        secretHmac: JSON.stringify(encrypted),
        subscribedEvents: dto.subscribedEvents,
        active: true,
        description: dto.description ?? null,
      },
    });

    return { ...this.toDto(row), secret };
  }

  async findOne(merchantId: string, id: string) {
    const row = await this.prisma.webhookEndpoint.findFirst({ where: { id, merchantId } });
    if (!row) throw new NotFoundException("Webhook endpoint not found");
    return this.toDto(row);
  }

  async list(merchantId: string, limit = 20, cursor?: string) {
    const take = Math.min(Math.max(limit, 1), 100);
    const rows = await this.prisma.webhookEndpoint.findMany({
      where: { merchantId },
      take: take + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { createdAt: "desc" },
    });
    const hasMore = rows.length > take;
    const data = hasMore ? rows.slice(0, take) : rows;
    return {
      data: data.map((r) => this.toDto(r)),
      nextCursor: hasMore ? data[data.length - 1].id : null,
    };
  }

  async update(merchantId: string, id: string, dto: UpdateWebhookEndpointDto) {
    await this.findOneRaw(merchantId, id);
    const row = await this.prisma.webhookEndpoint.update({
      where: { id },
      data: {
        ...(dto.url !== undefined && { url: dto.url }),
        ...(dto.subscribedEvents !== undefined && { subscribedEvents: dto.subscribedEvents }),
        ...(dto.active !== undefined && { active: dto.active }),
        ...(dto.description !== undefined && { description: dto.description ?? null }),
      },
    });
    return this.toDto(row);
  }

  async remove(merchantId: string, id: string) {
    await this.findOneRaw(merchantId, id);
    await this.prisma.webhookEndpoint.delete({ where: { id } });
    return { deleted: true, id };
  }

  /** Uso interno (dispatcher de eventos) — devuelve el secret HMAC en plano. */
  async getDecryptedSecret(merchantId: string, id: string): Promise<string> {
    const row = await this.findOneRaw(merchantId, id);
    const payload = JSON.parse(row.secretHmac) as EncryptedPayload;
    return this.encryption.decrypt(payload);
  }

  private async findOneRaw(merchantId: string, id: string): Promise<WebhookEndpoint> {
    const row = await this.prisma.webhookEndpoint.findFirst({ where: { id, merchantId } });
    if (!row) throw new NotFoundException("Webhook endpoint not found");
    return row;
  }

  private generateSecret(): string {
    return `whsec_${randomBytes(32).toString("hex")}`;
  }

  private toDto(row: WebhookEndpoint) {
    return {
      id: row.id,
      merchantId: row.merchantId,
      url: row.url,
      subscribedEvents: row.subscribedEvents,
      active: row.active,
      description: row.description,
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    };
  }
}
