import { Injectable, Inject, Logger, OnModuleInit } from '@nestjs/common';
import { RedisEventBus } from '@it0/events';
import { EventPatterns } from '@it0/common';
import { EscalationPolicyRepository } from '../../infrastructure/repositories/escalation-policy.repository';
import { ContactRepository } from '../../infrastructure/repositories/contact.repository';
import { AdapterRegistry } from '../../infrastructure/adapters/adapter-registry';
import { EscalationPolicy } from '../entities/escalation-policy.entity';
import { Contact } from '../entities/contact.entity';
import { DeliveryResult } from '../ports/outbound/channel-adapter.port';

interface EscalationData {
  alertId: string;
  tenantId: string;
  severity: string;
  message: string;
}

@Injectable()
export class EscalationEngine implements OnModuleInit {
  private readonly logger = new Logger(EscalationEngine.name);
  private readonly activeEscalations = new Map<string, { cancelled: boolean }>();

  constructor(
    private readonly policyRepo: EscalationPolicyRepository,
    private readonly contactRepo: ContactRepository,
    @Inject('EVENT_BUS') private readonly eventBus: RedisEventBus,
    private readonly adapterRegistry: AdapterRegistry,
  ) {}

  onModuleInit(): void {
    this.eventBus.subscribe(
      EventPatterns.ESCALATION_TRIGGERED,
      'comm-escalation-group',
      'escalation-engine',
      (data: EscalationData) => this.executeEscalation(data),
    );

    this.eventBus.subscribe(
      EventPatterns.HUMAN_RESPONSE_RECEIVED,
      'comm-escalation-cancel-group',
      'escalation-cancel',
      async (data: { alertId: string }) => this.cancelEscalation(data.alertId),
    );

    this.logger.log('EscalationEngine subscribed to ESCALATION_TRIGGERED and HUMAN_RESPONSE_RECEIVED');
  }

  async executeEscalation(data: EscalationData): Promise<void> {
    const { alertId, tenantId, severity, message } = data;
    this.logger.log(`Executing escalation for alert=${alertId} severity=${severity}`);

    this.activeEscalations.set(alertId, { cancelled: false });

    const policy = await this.resolvePolicy(severity);
    if (!policy) {
      this.logger.warn(`No escalation policy found for severity=${severity}, skipping`);
      return;
    }

    for (const step of policy.steps) {
      if (this.activeEscalations.get(alertId)?.cancelled) {
        this.logger.log(`Escalation for alert=${alertId} cancelled by human response`);
        break;
      }

      if (step.delaySeconds > 0) {
        await this.delay(step.delaySeconds * 1000);
      }

      if (this.activeEscalations.get(alertId)?.cancelled) {
        this.logger.log(`Escalation for alert=${alertId} cancelled during delay`);
        break;
      }

      const contacts = await this.resolveContacts(step.contactIds);
      const adapter = this.adapterRegistry.getAdapter(step.channel);

      if (adapter) {
        for (const contact of contacts) {
          try {
            const result: DeliveryResult = await adapter.send({
              to: contact,
              content: message,
              priority: severity,
            });
            this.logger.log(
              `Notification sent via ${step.channel} to ${contact.name}: ${result.status}`,
            );
          } catch (error) {
            this.logger.error(
              `Failed to send via ${step.channel} to ${contact.name}: ${error}`,
            );
          }
        }
      } else {
        // Fallback: publish event for external handling when no adapter found
        await this.eventBus.publish(EventPatterns.NOTIFICATION_SENT, {
          alertId,
          tenantId,
          channel: step.channel,
          contactIds: step.contactIds ?? [],
          message,
          severity,
          timestamp: new Date().toISOString(),
        });
      }
    }

    this.activeEscalations.delete(alertId);
  }

  private cancelEscalation(alertId: string): void {
    const escalation = this.activeEscalations.get(alertId);
    if (escalation) {
      escalation.cancelled = true;
      this.logger.log(`Escalation cancelled for alert=${alertId}`);
    }
  }

  private async resolvePolicy(severity: string): Promise<EscalationPolicy | null> {
    const policies = await this.policyRepo.findBySeverity(severity);
    if (policies.length > 0) {
      return policies[0];
    }
    return this.policyRepo.findDefault();
  }

  private async resolveContacts(contactIds?: string[]): Promise<Contact[]> {
    if (!contactIds || contactIds.length === 0) {
      return [];
    }
    const contacts: Contact[] = [];
    for (const id of contactIds) {
      const found = await this.contactRepo.findByUserId(id);
      contacts.push(...found.filter((c) => c.isActive));
    }
    return contacts;
  }

  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}
