import { Repository, FindOptionsWhere, In, LessThan } from 'typeorm';
import { WebhookEventType, WebhookEndpoint, WebhookDelivery, WebhookEvent } from '../entities';

export class WebhooksService {
  constructor(
    private readonly eventTypeRepository: Repository<WebhookEventType>,
    private readonly endpointRepository: Repository<WebhookEndpoint>,
    private readonly deliveryRepository: Repository<WebhookDelivery>,
    private readonly eventRepository: Repository<WebhookEvent>
  ) {}

  // ============================================
  // EVENT TYPES
  // ============================================

  async findAllEventTypes(): Promise<WebhookEventType[]> {
    return this.eventTypeRepository.find({
      where: { isActive: true },
      order: { category: 'ASC', code: 'ASC' },
    });
  }

  async findEventTypeByCode(code: string): Promise<WebhookEventType | null> {
    return this.eventTypeRepository.findOne({ where: { code } });
  }

  async findEventTypesByCategory(category: string): Promise<WebhookEventType[]> {
    return this.eventTypeRepository.find({
      where: { category: category as any, isActive: true },
      order: { code: 'ASC' },
    });
  }

  // ============================================
  // ENDPOINTS
  // ============================================

  async findAllEndpoints(tenantId: string): Promise<WebhookEndpoint[]> {
    return this.endpointRepository.find({
      where: { tenantId },
      order: { name: 'ASC' },
    });
  }

  async findActiveEndpoints(tenantId: string): Promise<WebhookEndpoint[]> {
    return this.endpointRepository.find({
      where: { tenantId, isActive: true },
      order: { name: 'ASC' },
    });
  }

  async findEndpoint(id: string): Promise<WebhookEndpoint | null> {
    return this.endpointRepository.findOne({ where: { id } });
  }

  async findEndpointByUrl(tenantId: string, url: string): Promise<WebhookEndpoint | null> {
    return this.endpointRepository.findOne({ where: { tenantId, url } });
  }

  async createEndpoint(
    tenantId: string,
    data: Partial<WebhookEndpoint>,
    createdBy?: string
  ): Promise<WebhookEndpoint> {
    const endpoint = this.endpointRepository.create({
      ...data,
      tenantId,
      createdBy,
    });
    return this.endpointRepository.save(endpoint);
  }

  async updateEndpoint(
    id: string,
    data: Partial<WebhookEndpoint>,
    updatedBy?: string
  ): Promise<WebhookEndpoint | null> {
    const endpoint = await this.findEndpoint(id);
    if (!endpoint) return null;

    Object.assign(endpoint, data, { updatedBy });
    return this.endpointRepository.save(endpoint);
  }

  async deleteEndpoint(id: string): Promise<boolean> {
    const result = await this.endpointRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  async toggleEndpoint(id: string, isActive: boolean): Promise<WebhookEndpoint | null> {
    const endpoint = await this.findEndpoint(id);
    if (!endpoint) return null;

    endpoint.isActive = isActive;
    return this.endpointRepository.save(endpoint);
  }

  async findEndpointsForEvent(tenantId: string, eventTypeCode: string): Promise<WebhookEndpoint[]> {
    return this.endpointRepository
      .createQueryBuilder('endpoint')
      .where('endpoint.tenant_id = :tenantId', { tenantId })
      .andWhere('endpoint.is_active = true')
      .andWhere(':eventTypeCode = ANY(endpoint.subscribed_events)', { eventTypeCode })
      .getMany();
  }

  async updateEndpointHealth(
    id: string,
    isHealthy: boolean,
    consecutiveFailures: number
  ): Promise<void> {
    // Update using available fields on WebhookEndpoint entity
    // isHealthy maps to isActive, tracking failures via delivery stats
    await this.endpointRepository.update(id, {
      isActive: isHealthy,
      failedDeliveries: consecutiveFailures > 0 ? consecutiveFailures : undefined,
    });
  }

  // ============================================
  // EVENTS
  // ============================================

  async createEvent(tenantId: string, data: Partial<WebhookEvent>): Promise<WebhookEvent> {
    const event = this.eventRepository.create({
      ...data,
      tenantId,
      status: 'pending',
    });
    return this.eventRepository.save(event);
  }

  async findEvent(id: string): Promise<WebhookEvent | null> {
    return this.eventRepository.findOne({
      where: { id },
      relations: ['deliveries'],
    });
  }

  async findPendingEvents(limit: number = 100): Promise<WebhookEvent[]> {
    return this.eventRepository.find({
      where: { status: 'pending' },
      order: { createdAt: 'ASC' },
      take: limit,
    });
  }

  async updateEventStatus(id: string, status: string): Promise<void> {
    const updates: Partial<WebhookEvent> = { status: status as any };
    if (status === 'processed') {
      updates.processedAt = new Date();
    }
    await this.eventRepository.update(id, updates);
  }

  // ============================================
  // DELIVERIES
  // ============================================

  async createDelivery(data: Partial<WebhookDelivery>): Promise<WebhookDelivery> {
    const delivery = this.deliveryRepository.create({
      ...data,
      status: 'pending',
    });
    return this.deliveryRepository.save(delivery);
  }

  async findDelivery(id: string): Promise<WebhookDelivery | null> {
    return this.deliveryRepository.findOne({ where: { id } });
  }

  async findDeliveriesForEvent(eventId: string): Promise<WebhookDelivery[]> {
    return this.deliveryRepository.find({
      where: { eventId },
      order: { attemptNumber: 'ASC' },
    });
  }

  async findDeliveriesForEndpoint(
    endpointId: string,
    limit: number = 50
  ): Promise<WebhookDelivery[]> {
    return this.deliveryRepository.find({
      where: { endpointId },
      order: { createdAt: 'DESC' },
      take: limit,
      relations: ['event'],
    });
  }

  async updateDeliveryResult(
    id: string,
    status: string,
    responseStatus?: number,
    responseBody?: string,
    duration?: number,
    errorMessage?: string
  ): Promise<void> {
    const updates: Partial<WebhookDelivery> = {
      status: status as any,
      responseStatus,
      responseBody,
      responseTimeMs: duration,
      errorMessage,
      completedAt: new Date(),
    };

    await this.deliveryRepository.update(id, updates);
  }

  async findPendingRetries(limit: number = 100): Promise<WebhookDelivery[]> {
    const now = new Date();
    return this.deliveryRepository.find({
      where: {
        status: 'pending',
        nextRetryAt: LessThan(now),
      },
      order: { nextRetryAt: 'ASC' },
      take: limit,
    });
  }

  async scheduleRetry(id: string, nextRetryAt: Date, attemptNumber: number): Promise<void> {
    await this.deliveryRepository.update(id, {
      nextRetryAt,
      attemptNumber,
      status: 'pending',
    });
  }

  // ============================================
  // STATISTICS
  // ============================================

  async getEndpointStats(
    endpointId: string,
    startDate: Date,
    endDate: Date
  ): Promise<{
    total: number;
    success: number;
    failed: number;
    avgDuration: number;
  }> {
    const result = await this.deliveryRepository
      .createQueryBuilder('d')
      .select('COUNT(*)', 'total')
      .addSelect('SUM(CASE WHEN d.status = :success THEN 1 ELSE 0 END)', 'success')
      .addSelect('SUM(CASE WHEN d.status = :failed THEN 1 ELSE 0 END)', 'failed')
      .addSelect('AVG(d.duration_ms)', 'avgDuration')
      .where('d.endpoint_id = :endpointId', { endpointId })
      .andWhere('d.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .setParameter('success', 'success')
      .setParameter('failed', 'failed')
      .getRawOne();

    return {
      total: parseInt(result.total) || 0,
      success: parseInt(result.success) || 0,
      failed: parseInt(result.failed) || 0,
      avgDuration: parseFloat(result.avgDuration) || 0,
    };
  }
}
