/**
 * WebhookSubscriptionService - Webhook Subscription Management
 *
 * Manages subscriptions linking endpoints to specific event types.
 * Handles subscription creation, activation, and filtering.
 *
 * @module Webhooks
 */

import { Repository } from 'typeorm';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { WebhookSubscription } from '../entities/subscription.entity';
import { WebhookEndpoint } from '../entities/endpoint.entity';
import { WebhookEventType } from '../entities/event-type.entity';

// DTOs
export interface CreateSubscriptionDto {
  endpointId: string;
  eventTypeId: string;
  filters?: Record<string, any>;
  payloadTemplate?: Record<string, any>;
}

export interface UpdateSubscriptionDto {
  filters?: Record<string, any>;
  payloadTemplate?: Record<string, any>;
  isActive?: boolean;
}

export interface SubscriptionFilters {
  endpointId?: string;
  eventTypeId?: string;
  isActive?: boolean;
  page?: number;
  limit?: number;
}

export interface BulkSubscribeDto {
  endpointId: string;
  eventTypeIds: string[];
  filters?: Record<string, any>;
}

export interface SubscriptionWithDetails extends Omit<WebhookSubscription, 'endpoint' | 'eventType'> {
  endpoint?: WebhookEndpoint;
  eventType?: WebhookEventType;
}

export class WebhookSubscriptionService {
  constructor(
    private readonly repository: Repository<WebhookSubscription>,
    private readonly endpointRepository: Repository<WebhookEndpoint>,
    private readonly eventTypeRepository: Repository<WebhookEventType>
  ) {}

  /**
   * Create a new subscription
   */
  async create(ctx: ServiceContext, dto: CreateSubscriptionDto): Promise<WebhookSubscription> {
    // Validate endpoint exists and belongs to tenant
    const endpoint = await this.endpointRepository.findOne({
      where: { id: dto.endpointId, tenantId: ctx.tenantId },
    });

    if (!endpoint) {
      throw new Error('Endpoint not found');
    }

    // Validate event type exists
    const eventType = await this.eventTypeRepository.findOne({
      where: { id: dto.eventTypeId },
    });

    if (!eventType) {
      throw new Error('Event type not found');
    }

    // Check for duplicate subscription
    const existing = await this.repository.findOne({
      where: {
        endpointId: dto.endpointId,
        eventTypeId: dto.eventTypeId,
      },
    });

    if (existing) {
      throw new Error('Subscription already exists for this endpoint and event type');
    }

    const subscription = this.repository.create({
      endpointId: dto.endpointId,
      eventTypeId: dto.eventTypeId,
      tenantId: ctx.tenantId,
      filters: dto.filters || {},
      payloadTemplate: dto.payloadTemplate,
      isActive: true,
    });

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

    // Update endpoint's subscribed events array
    await this.syncEndpointSubscribedEvents(dto.endpointId);

    return saved;
  }

  /**
   * Bulk subscribe endpoint to multiple event types
   */
  async bulkSubscribe(ctx: ServiceContext, dto: BulkSubscribeDto): Promise<WebhookSubscription[]> {
    // Validate endpoint
    const endpoint = await this.endpointRepository.findOne({
      where: { id: dto.endpointId, tenantId: ctx.tenantId },
    });

    if (!endpoint) {
      throw new Error('Endpoint not found');
    }

    // Validate all event types exist
    const eventTypes = await this.eventTypeRepository.findByIds(dto.eventTypeIds);
    if (eventTypes.length !== dto.eventTypeIds.length) {
      throw new Error('One or more event types not found');
    }

    const subscriptions: WebhookSubscription[] = [];

    for (const eventTypeId of dto.eventTypeIds) {
      // Skip if already subscribed
      const existing = await this.repository.findOne({
        where: { endpointId: dto.endpointId, eventTypeId },
      });

      if (existing) {
        subscriptions.push(existing);
        continue;
      }

      const subscription = this.repository.create({
        endpointId: dto.endpointId,
        eventTypeId,
        tenantId: ctx.tenantId,
        filters: dto.filters || {},
        isActive: true,
      });

      subscriptions.push(await this.repository.save(subscription));
    }

    // Sync endpoint's subscribed events
    await this.syncEndpointSubscribedEvents(dto.endpointId);

    return subscriptions;
  }

  /**
   * Sync endpoint's subscribedEvents array with actual subscriptions
   */
  private async syncEndpointSubscribedEvents(endpointId: string): Promise<void> {
    const subscriptions = await this.repository.find({
      where: { endpointId, isActive: true },
      relations: ['eventType'],
    });

    const eventCodes = subscriptions
      .filter((s) => s.eventType)
      .map((s) => s.eventType.code);

    await this.endpointRepository.update(endpointId, {
      subscribedEvents: eventCodes,
    });
  }

  /**
   * Find all subscriptions with filters and pagination
   */
  async findAll(
    ctx: ServiceContext,
    filters: SubscriptionFilters = {}
  ): Promise<PaginatedResult<SubscriptionWithDetails>> {
    const page = filters.page || 1;
    const limit = filters.limit || 50;

    const qb = this.repository
      .createQueryBuilder('s')
      .leftJoinAndSelect('s.endpoint', 'endpoint')
      .leftJoinAndSelect('s.eventType', 'eventType')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.endpointId) {
      qb.andWhere('s.endpoint_id = :endpointId', { endpointId: filters.endpointId });
    }

    if (filters.eventTypeId) {
      qb.andWhere('s.event_type_id = :eventTypeId', { eventTypeId: filters.eventTypeId });
    }

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

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

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

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

  /**
   * Find subscription by ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<SubscriptionWithDetails | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
      relations: ['endpoint', 'eventType'],
    });
  }

  /**
   * Find subscriptions for an endpoint
   */
  async findByEndpointId(ctx: ServiceContext, endpointId: string): Promise<SubscriptionWithDetails[]> {
    return this.repository.find({
      where: { endpointId, tenantId: ctx.tenantId },
      relations: ['eventType'],
      order: { createdAt: 'ASC' },
    });
  }

  /**
   * Find subscriptions for an event type
   */
  async findByEventTypeId(ctx: ServiceContext, eventTypeId: string): Promise<SubscriptionWithDetails[]> {
    return this.repository.find({
      where: { eventTypeId, tenantId: ctx.tenantId, isActive: true },
      relations: ['endpoint'],
    });
  }

  /**
   * Find active subscriptions for an event type code
   */
  async findActiveByEventCode(
    ctx: ServiceContext,
    eventCode: string
  ): Promise<SubscriptionWithDetails[]> {
    return this.repository
      .createQueryBuilder('s')
      .leftJoinAndSelect('s.endpoint', 'endpoint')
      .leftJoinAndSelect('s.eventType', 'eventType')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('s.is_active = true')
      .andWhere('endpoint.is_active = true')
      .andWhere('eventType.code = :eventCode', { eventCode })
      .getMany();
  }

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

    if (dto.filters !== undefined) subscription.filters = dto.filters;
    if (dto.payloadTemplate !== undefined) subscription.payloadTemplate = dto.payloadTemplate;
    if (dto.isActive !== undefined) subscription.isActive = dto.isActive;

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

    // Sync endpoint's subscribed events if activation changed
    if (dto.isActive !== undefined) {
      await this.syncEndpointSubscribedEvents(subscription.endpointId);
    }

    return saved;
  }

  /**
   * Activate subscription
   */
  async activate(ctx: ServiceContext, id: string): Promise<WebhookSubscription | null> {
    return this.update(ctx, id, { isActive: true });
  }

  /**
   * Deactivate subscription
   */
  async deactivate(ctx: ServiceContext, id: string): Promise<WebhookSubscription | null> {
    return this.update(ctx, id, { isActive: false });
  }

  /**
   * Delete subscription
   */
  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const subscription = await this.findById(ctx, id);
    if (!subscription) return false;

    const endpointId = subscription.endpointId;

    const result = await this.repository.delete({
      id,
      tenantId: ctx.tenantId,
    });

    // Sync endpoint's subscribed events
    await this.syncEndpointSubscribedEvents(endpointId);

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

  /**
   * Unsubscribe endpoint from all event types
   */
  async unsubscribeAll(ctx: ServiceContext, endpointId: string): Promise<number> {
    const result = await this.repository.delete({
      endpointId,
      tenantId: ctx.tenantId,
    });

    // Clear endpoint's subscribed events
    await this.endpointRepository.update(endpointId, {
      subscribedEvents: [],
    });

    return result.affected || 0;
  }

  /**
   * Get subscription count by endpoint
   */
  async getCountByEndpoint(ctx: ServiceContext, endpointId: string): Promise<number> {
    return this.repository.count({
      where: { endpointId, tenantId: ctx.tenantId, isActive: true },
    });
  }

  /**
   * Check if endpoint is subscribed to event type
   */
  async isSubscribed(
    ctx: ServiceContext,
    endpointId: string,
    eventTypeId: string
  ): Promise<boolean> {
    const subscription = await this.repository.findOne({
      where: { endpointId, eventTypeId, tenantId: ctx.tenantId },
    });

    return subscription !== null && subscription.isActive;
  }
}
