import { Injectable, Logger, NotFoundException, BadRequestException, Inject } from '@nestjs/common';
import { Webhook } from '../../../database/models/webhook.model';
import { WebhookLog, WebhookLogStatus } from '../../../database/models/webhook-log.model';
import { CreateWebhookDto, UpdateWebhookDto, WebhookTestResultDto } from '../dto';
import { PaginationDto } from '../../../common/dto/pagination.dto';
import * as crypto from 'crypto';
import { paginate, PaginatedResult } from '../../../common/utils/pagination.util';

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

  constructor(
    @Inject(Webhook)
    private webhookRepository: typeof Webhook,
    @Inject(WebhookLog)
    private webhookLogRepository: typeof WebhookLog,
  ) {}

  async register(departmentId: string, dto: CreateWebhookDto): Promise<Webhook> {
    try {
      this.logger.debug(`Registering webhook for department: ${departmentId}, URL: ${dto.url}`);

      const secret = crypto.randomBytes(32).toString('hex');

      const saved = await this.webhookRepository.query().insertAndFetch({
        departmentId,
        url: dto.url,
        events: dto.events as any,
        secretHash: secret,
        isActive: true,
      });
      this.logger.log(`Webhook registered: ${saved.id}`);

      return saved;
    } catch (error) {
      this.logger.error('Failed to register webhook', error);
      throw error;
    }
  }

  async findAll(departmentId: string): Promise<Webhook[]> {
    try {
      return await this.webhookRepository
        .query()
        .where({ departmentId })
        .orderBy('created_at', 'DESC');
    } catch (error) {
      this.logger.error(`Failed to find webhooks for department ${departmentId}`, error);
      throw error;
    }
  }

  async findAllPaginated(pagination: PaginationDto): Promise<PaginatedResult<Webhook>> {
    try {
      const query = this.webhookRepository.query().orderBy('created_at', 'DESC');

      return await paginate(query, pagination.page, pagination.limit);
    } catch (error) {
      this.logger.error('Failed to find all webhooks', error);
      throw error;
    }
  }

  async findById(id: string): Promise<Webhook> {
    try {
      const webhook = await this.webhookRepository.query().findById(id);

      if (!webhook) {
        throw new NotFoundException(`Webhook not found: ${id}`);
      }

      return webhook;
    } catch (error) {
      if (error instanceof NotFoundException) {
        throw error;
      }
      this.logger.error(`Failed to find webhook: ${id}`, error);
      throw error;
    }
  }

  async update(id: string, dto: UpdateWebhookDto): Promise<Webhook> {
    try {
      this.logger.debug(`Updating webhook: ${id}`);

      const webhook = await this.findById(id);
      const updated = await webhook.$query().patchAndFetch(dto as any);
      this.logger.log(`Webhook updated: ${id}`);

      return updated;
    } catch (error) {
      this.logger.error(`Failed to update webhook: ${id}`, error);
      throw error;
    }
  }

  async delete(id: string): Promise<void> {
    try {
      this.logger.debug(`Deleting webhook: ${id}`);

      const deletedCount = await this.webhookRepository.query().deleteById(id);
      if (deletedCount === 0) {
        throw new NotFoundException(`Webhook not found: ${id}`);
      }

      this.logger.log(`Webhook deleted: ${id}`);
    } catch (error) {
      this.logger.error(`Failed to delete webhook: ${id}`, error);
      throw error;
    }
  }

  async test(id: string): Promise<WebhookTestResultDto> {
    try {
      this.logger.debug(`Testing webhook: ${id}`);

      const webhook = await this.findById(id);

      const testPayload = {
        eventType: 'TEST_EVENT',
        timestamp: new Date(),
        data: { test: true },
      };

      const startTime = Date.now();

      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 10000);

        const response = await fetch(webhook.url, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-Webhook-Signature': this.generateSignature(testPayload, webhook.secretHash),
          },
          body: JSON.stringify(testPayload),
          signal: controller.signal,
        });

        clearTimeout(timeoutId);
        const responseTime = Date.now() - startTime;

        const result: WebhookTestResultDto = {
          success: response.ok,
          statusCode: response.status,
          statusMessage: response.statusText,
          responseTime,
        };

        this.logger.log(`Webhook test successful: ${id}, status=${response.status}`);
        return result;
      } catch (fetchError) {
        const responseTime = Date.now() - startTime;
        const error = fetchError as Error;

        this.logger.warn(`Webhook test failed: ${id}, error=${error.message}`);

        return {
          success: false,
          statusCode: 0,
          statusMessage: 'Error',
          responseTime,
          error: error.message,
        };
      }
    } catch (error) {
      this.logger.error(`Failed to test webhook: ${id}`, error);
      throw error;
    }
  }

  async getLogs(
    webhookId: string,
    pagination: PaginationDto,
  ): Promise<PaginatedResult<WebhookLog>> {
    try {
      await this.findById(webhookId);

      const query = this.webhookLogRepository
        .query()
        .where({ webhookId })
        .orderBy('created_at', 'DESC');

      return await paginate(query, pagination.page, pagination.limit);
    } catch (error) {
      this.logger.error(`Failed to get logs for webhook: ${webhookId}`, error);
      throw error;
    }
  }

  generateSignature(payload: object, secret: string): string {
    const message = JSON.stringify(payload);
    return crypto.createHmac('sha256', secret).update(message).digest('hex');
  }

  verifySignature(payload: object, signature: string, secret: string): boolean {
    const expectedSignature = this.generateSignature(payload, secret);
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
  }

  async logWebhookAttempt(
    webhookId: string,
    eventType: string,
    payload: object,
    responseStatus: number | null,
    responseBody: string | null,
    responseTime: number | null,
    retryCount: number,
  ): Promise<WebhookLog> {
    try {
      const status =
        responseStatus && responseStatus >= 200 && responseStatus < 300
          ? WebhookLogStatus.SUCCESS
          : WebhookLogStatus.FAILED;

      return await this.webhookLogRepository.query().insert({
        webhookId,
        eventType,
        payload: payload as any,
        responseStatus,
        responseBody,
        responseTime,
        retryCount,
        status: status as any,
      });
    } catch (error) {
      this.logger.error(`Failed to log webhook attempt: ${webhookId}`, error);
      throw error;
    }
  }
}
