import {
  Injectable,
  NotFoundException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateWebhookDto } from './dto/create-webhook.dto';
import { UpdateWebhookDto } from './dto/update-webhook.dto';
import { FindWebhooksDto, FindWebhookLogsDto } from './dto/find-webhooks.dto';
import { Prisma } from '@prisma/client';
import { randomBytes, createHmac } from 'crypto';

export interface PaginatedResult<T> {
  data: T[];
  nextCursor: string | null;
  hasMore: boolean;
}

@Injectable()
export class WebhooksService {
  private readonly logger = new Logger(WebhooksService.name);

  constructor(private readonly prisma: PrismaService) {}

  // ─── Subscriptions ───────────────────────────────────────────────

  async findAll(
    clubId: string,
    params: FindWebhooksDto,
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20 } = params;

    const items = await this.prisma.webhookSubscription.findMany({
      where: { clubId },
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { createdAt: 'desc' },
      include: { _count: { select: { logs: true } } },
    });

    const hasMore = items.length > limit;
    const data = hasMore ? items.slice(0, -1) : items;

    return {
      data: data.map((s) => ({ ...s, secret: undefined })),
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  async findById(clubId: string, id: string) {
    const sub = await this.prisma.webhookSubscription.findFirst({
      where: { id, clubId },
      include: { _count: { select: { logs: true } } },
    });

    if (!sub) {
      throw new NotFoundException(`Webhook subscription with id ${id} not found`);
    }

    return { ...sub, secret: undefined };
  }

  async create(clubId: string, dto: CreateWebhookDto) {
    const secret = randomBytes(32).toString('hex');

    const subscription = await this.prisma.webhookSubscription.create({
      data: {
        clubId,
        url: dto.url,
        events: dto.events,
        secret,
        isActive: true,
      },
    });

    // Return secret only on creation so the user can save it
    return subscription;
  }

  async update(clubId: string, id: string, dto: UpdateWebhookDto) {
    await this.ensureSubscriptionExists(clubId, id);

    return this.prisma.webhookSubscription.update({
      where: { id },
      data: {
        ...(dto.url !== undefined ? { url: dto.url } : {}),
        ...(dto.events !== undefined ? { events: dto.events } : {}),
        ...(dto.isActive !== undefined ? { isActive: dto.isActive } : {}),
      },
    });
  }

  async remove(clubId: string, id: string) {
    await this.ensureSubscriptionExists(clubId, id);
    await this.prisma.webhookSubscription.delete({ where: { id } });
  }

  // ─── Logs ────────────────────────────────────────────────────────

  async findLogs(
    clubId: string,
    subscriptionId: string,
    params: FindWebhookLogsDto,
  ): Promise<PaginatedResult<any>> {
    await this.ensureSubscriptionExists(clubId, subscriptionId);
    const { cursor, limit = 20, event } = params;

    const where: Prisma.WebhookLogWhereInput = {
      clubId,
      subscriptionId,
      ...(event ? { event } : {}),
    };

    const items = await this.prisma.webhookLog.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { createdAt: 'desc' },
    });

    const hasMore = items.length > limit;
    const data = hasMore ? items.slice(0, -1) : items;

    return {
      data,
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  // ─── Test delivery ───────────────────────────────────────────────

  async testDelivery(clubId: string, id: string) {
    const sub = await this.prisma.webhookSubscription.findFirst({
      where: { id, clubId },
    });

    if (!sub) {
      throw new NotFoundException(`Webhook subscription with id ${id} not found`);
    }

    const testPayload = {
      event: 'test',
      timestamp: new Date().toISOString(),
      club_id: clubId,
      data: { message: 'Test webhook delivery' },
    };

    const signature = this.signPayload(
      JSON.stringify(testPayload),
      sub.secret,
    );

    let statusCode: number | null = null;
    let response: string | null = null;

    try {
      const res = await fetch(sub.url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Webhook-Signature': signature,
        },
        body: JSON.stringify(testPayload),
        signal: AbortSignal.timeout(10000),
      });

      statusCode = res.status;
      response = await res.text();
    } catch (err: any) {
      response = err.message;
    }

    const log = await this.prisma.webhookLog.create({
      data: {
        clubId,
        subscriptionId: id,
        event: 'test',
        payload: testPayload,
        statusCode,
        response: response?.slice(0, 1000),
        attempt: 1,
        deliveredAt: statusCode && statusCode >= 200 && statusCode < 300
          ? new Date()
          : null,
      },
    });

    return {
      success: statusCode !== null && statusCode >= 200 && statusCode < 300,
      statusCode,
      log,
    };
  }

  // ─── Helpers ─────────────────────────────────────────────────────

  private signPayload(payload: string, secret: string): string {
    return createHmac('sha256', secret).update(payload).digest('hex');
  }

  private async ensureSubscriptionExists(
    clubId: string,
    id: string,
  ): Promise<void> {
    const count = await this.prisma.webhookSubscription.count({
      where: { id, clubId },
    });
    if (count === 0) {
      throw new NotFoundException(`Webhook subscription with id ${id} not found`);
    }
  }
}
