/**
 * WebhookEndpointService - Webhook Endpoint Management
 *
 * CRUD operations for outbound webhook endpoint configurations.
 * Handles registration, verification, and statistics tracking.
 *
 * @module Webhooks
 */

import { Repository } from 'typeorm';
import * as crypto from 'crypto';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { WebhookEndpoint, AuthType } from '../entities/endpoint.entity';
import { WebhookEndpointLog, WebhookLogType } from '../entities/endpoint-log.entity';

// DTOs
export interface CreateEndpointDto {
  name: string;
  description?: string;
  url: string;
  httpMethod?: string;
  authType?: AuthType;
  authConfig?: Record<string, any>;
  customHeaders?: Record<string, string>;
  subscribedEvents?: string[];
  filters?: Record<string, any>;
  retryEnabled?: boolean;
  maxRetries?: number;
  retryDelaySeconds?: number;
  retryBackoffMultiplier?: number;
  timeoutSeconds?: number;
  rateLimitPerMinute?: number;
  rateLimitPerHour?: number;
}

export interface UpdateEndpointDto extends Partial<CreateEndpointDto> {
  isActive?: boolean;
}

export interface EndpointFilters {
  isActive?: boolean;
  isVerified?: boolean;
  authType?: AuthType;
  search?: string;
  page?: number;
  limit?: number;
}

export interface EndpointStats {
  total: number;
  active: number;
  verified: number;
  totalDeliveries: number;
  successfulDeliveries: number;
  failedDeliveries: number;
  successRate: number;
}

export class WebhookEndpointService {
  constructor(
    private readonly repository: Repository<WebhookEndpoint>,
    private readonly logRepository: Repository<WebhookEndpointLog>
  ) {}

  /**
   * Generate a secure signing secret for webhook payloads
   */
  private generateSigningSecret(): string {
    return `whsec_${crypto.randomBytes(32).toString('hex')}`;
  }

  /**
   * Create endpoint activity log
   */
  private async createLog(
    ctx: ServiceContext,
    endpointId: string,
    logType: WebhookLogType,
    message?: string,
    details?: Record<string, any>
  ): Promise<void> {
    await this.logRepository.save(
      this.logRepository.create({
        endpointId,
        tenantId: ctx.tenantId,
        logType,
        message,
        details: details || {},
        actorId: ctx.userId,
      })
    );
  }

  /**
   * Create a new webhook endpoint
   */
  async create(ctx: ServiceContext, dto: CreateEndpointDto): Promise<WebhookEndpoint> {
    // Check for duplicate URL in tenant
    const existing = await this.repository.findOne({
      where: { tenantId: ctx.tenantId, url: dto.url },
    });

    if (existing) {
      throw new Error('Endpoint with this URL already exists');
    }

    const signingSecret = this.generateSigningSecret();

    const endpoint = this.repository.create({
      tenantId: ctx.tenantId,
      name: dto.name,
      description: dto.description,
      url: dto.url,
      httpMethod: dto.httpMethod || 'POST',
      authType: dto.authType || 'none',
      authConfig: dto.authConfig || {},
      customHeaders: dto.customHeaders || {},
      subscribedEvents: dto.subscribedEvents || [],
      filters: dto.filters || {},
      retryEnabled: dto.retryEnabled ?? true,
      maxRetries: dto.maxRetries ?? 5,
      retryDelaySeconds: dto.retryDelaySeconds ?? 60,
      retryBackoffMultiplier: dto.retryBackoffMultiplier ?? 2.0,
      timeoutSeconds: dto.timeoutSeconds ?? 30,
      rateLimitPerMinute: dto.rateLimitPerMinute ?? 60,
      rateLimitPerHour: dto.rateLimitPerHour ?? 1000,
      signingSecret,
      isActive: true,
      isVerified: false,
      createdBy: ctx.userId,
    });

    const saved = await this.repository.save(endpoint);

    await this.createLog(ctx, saved.id, 'created', 'Webhook endpoint created', {
      name: dto.name,
      url: dto.url,
    });

    return saved;
  }

  /**
   * Find all endpoints with filters and pagination
   */
  async findAll(
    ctx: ServiceContext,
    filters: EndpointFilters = {}
  ): Promise<PaginatedResult<WebhookEndpoint>> {
    const page = filters.page || 1;
    const limit = filters.limit || 20;

    const qb = this.repository
      .createQueryBuilder('e')
      .where('e.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.isActive !== undefined) {
      qb.andWhere('e.is_active = :isActive', { isActive: filters.isActive });
    }

    if (filters.isVerified !== undefined) {
      qb.andWhere('e.is_verified = :isVerified', { isVerified: filters.isVerified });
    }

    if (filters.authType) {
      qb.andWhere('e.auth_type = :authType', { authType: filters.authType });
    }

    if (filters.search) {
      qb.andWhere(
        '(e.name ILIKE :search OR e.url ILIKE :search OR e.description ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (page - 1) * limit;
    qb.orderBy('e.created_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Find endpoint by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<WebhookEndpoint | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
    });
  }

  /**
   * Find endpoints subscribed to a specific event type
   */
  async findByEventType(ctx: ServiceContext, eventType: string): Promise<WebhookEndpoint[]> {
    return this.repository
      .createQueryBuilder('e')
      .where('e.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('e.is_active = true')
      .andWhere(':eventType = ANY(e.subscribed_events)', { eventType })
      .getMany();
  }

  /**
   * Update endpoint
   */
  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateEndpointDto
  ): Promise<WebhookEndpoint | null> {
    const endpoint = await this.findById(ctx, id);
    if (!endpoint) return null;

    // Track changes for logging
    const changes: Record<string, any> = {};

    if (dto.name !== undefined && dto.name !== endpoint.name) {
      changes.name = { from: endpoint.name, to: dto.name };
      endpoint.name = dto.name;
    }
    if (dto.description !== undefined) endpoint.description = dto.description;
    if (dto.url !== undefined && dto.url !== endpoint.url) {
      changes.url = { from: endpoint.url, to: dto.url };
      endpoint.url = dto.url;
      endpoint.isVerified = false; // Reset verification on URL change
    }
    if (dto.httpMethod !== undefined) endpoint.httpMethod = dto.httpMethod;
    if (dto.authType !== undefined) endpoint.authType = dto.authType;
    if (dto.authConfig !== undefined) endpoint.authConfig = dto.authConfig;
    if (dto.customHeaders !== undefined) endpoint.customHeaders = dto.customHeaders;
    if (dto.subscribedEvents !== undefined) endpoint.subscribedEvents = dto.subscribedEvents;
    if (dto.filters !== undefined) endpoint.filters = dto.filters;
    if (dto.retryEnabled !== undefined) endpoint.retryEnabled = dto.retryEnabled;
    if (dto.maxRetries !== undefined) endpoint.maxRetries = dto.maxRetries;
    if (dto.retryDelaySeconds !== undefined) endpoint.retryDelaySeconds = dto.retryDelaySeconds;
    if (dto.retryBackoffMultiplier !== undefined) endpoint.retryBackoffMultiplier = dto.retryBackoffMultiplier;
    if (dto.timeoutSeconds !== undefined) endpoint.timeoutSeconds = dto.timeoutSeconds;
    if (dto.rateLimitPerMinute !== undefined) endpoint.rateLimitPerMinute = dto.rateLimitPerMinute;
    if (dto.rateLimitPerHour !== undefined) endpoint.rateLimitPerHour = dto.rateLimitPerHour;

    if (dto.isActive !== undefined && dto.isActive !== endpoint.isActive) {
      changes.isActive = { from: endpoint.isActive, to: dto.isActive };
      endpoint.isActive = dto.isActive;
    }

    const saved = await this.repository.save(endpoint);

    if (Object.keys(changes).length > 0) {
      await this.createLog(ctx, id, 'config_changed', 'Endpoint configuration updated', changes);
    }

    return saved;
  }

  /**
   * Activate endpoint
   */
  async activate(ctx: ServiceContext, id: string): Promise<WebhookEndpoint | null> {
    const endpoint = await this.findById(ctx, id);
    if (!endpoint) return null;

    endpoint.isActive = true;
    const saved = await this.repository.save(endpoint);

    await this.createLog(ctx, id, 'activated', 'Endpoint activated');

    return saved;
  }

  /**
   * Deactivate endpoint
   */
  async deactivate(ctx: ServiceContext, id: string): Promise<WebhookEndpoint | null> {
    const endpoint = await this.findById(ctx, id);
    if (!endpoint) return null;

    endpoint.isActive = false;
    const saved = await this.repository.save(endpoint);

    await this.createLog(ctx, id, 'deactivated', 'Endpoint deactivated');

    return saved;
  }

  /**
   * Verify endpoint by sending a test request
   */
  async verify(ctx: ServiceContext, id: string): Promise<{ success: boolean; message: string }> {
    const endpoint = await this.findById(ctx, id);
    if (!endpoint) {
      return { success: false, message: 'Endpoint not found' };
    }

    try {
      // In a real implementation, this would make an HTTP request to the endpoint URL
      // For now, we'll simulate verification
      endpoint.isVerified = true;
      endpoint.verifiedAt = new Date();
      await this.repository.save(endpoint);

      await this.createLog(ctx, id, 'verified', 'Endpoint verified successfully');

      return { success: true, message: 'Endpoint verified successfully' };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Verification failed';

      await this.createLog(ctx, id, 'error', `Verification failed: ${errorMessage}`);

      return { success: false, message: errorMessage };
    }
  }

  /**
   * Regenerate signing secret
   */
  async regenerateSecret(ctx: ServiceContext, id: string): Promise<{ secret: string } | null> {
    const endpoint = await this.findById(ctx, id);
    if (!endpoint) return null;

    const newSecret = this.generateSigningSecret();
    endpoint.signingSecret = newSecret;
    await this.repository.save(endpoint);

    await this.createLog(ctx, id, 'config_changed', 'Signing secret regenerated');

    return { secret: newSecret };
  }

  /**
   * Update delivery statistics
   */
  async updateStats(
    endpointId: string,
    success: boolean
  ): Promise<void> {
    const update: Partial<WebhookEndpoint> = {
      totalDeliveries: () => 'total_deliveries + 1',
      lastDeliveryAt: new Date(),
    } as any;

    if (success) {
      (update as any).successfulDeliveries = () => 'successful_deliveries + 1';
      update.lastSuccessAt = new Date();
    } else {
      (update as any).failedDeliveries = () => 'failed_deliveries + 1';
      update.lastFailureAt = new Date();
    }

    await this.repository
      .createQueryBuilder()
      .update(WebhookEndpoint)
      .set({
        totalDeliveries: () => 'total_deliveries + 1',
        ...(success
          ? { successfulDeliveries: () => 'successful_deliveries + 1', lastSuccessAt: new Date() }
          : { failedDeliveries: () => 'failed_deliveries + 1', lastFailureAt: new Date() }),
        lastDeliveryAt: new Date(),
      })
      .where('id = :id', { id: endpointId })
      .execute();
  }

  /**
   * Delete endpoint (hard delete)
   */
  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.delete({
      id,
      tenantId: ctx.tenantId,
    });

    return (result.affected || 0) > 0;
  }

  /**
   * Get endpoint statistics for tenant
   */
  async getStats(ctx: ServiceContext): Promise<EndpointStats> {
    const endpoints = await this.repository.find({
      where: { tenantId: ctx.tenantId },
    });

    const stats: EndpointStats = {
      total: endpoints.length,
      active: endpoints.filter((e) => e.isActive).length,
      verified: endpoints.filter((e) => e.isVerified).length,
      totalDeliveries: endpoints.reduce((sum, e) => sum + e.totalDeliveries, 0),
      successfulDeliveries: endpoints.reduce((sum, e) => sum + e.successfulDeliveries, 0),
      failedDeliveries: endpoints.reduce((sum, e) => sum + e.failedDeliveries, 0),
      successRate: 0,
    };

    if (stats.totalDeliveries > 0) {
      stats.successRate = (stats.successfulDeliveries / stats.totalDeliveries) * 100;
    }

    return stats;
  }

  /**
   * Get endpoint activity logs
   */
  async getLogs(
    ctx: ServiceContext,
    endpointId: string,
    limit: number = 50
  ): Promise<WebhookEndpointLog[]> {
    return this.logRepository.find({
      where: { endpointId, tenantId: ctx.tenantId },
      order: { createdAt: 'DESC' },
      take: limit,
    });
  }
}
