import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { HookManager, type HookContext, type HookResult } from '../../core/hooks';
import { createLogger } from '../../common/services/logger.service';
import { SessionService } from '../session/session.service';
import { WebhookService } from '../webhook/webhook.service';
import type { WebhookPayload } from '../webhook/webhook-delivery.service';
import { UltramsgMediaService } from './ultramsg-media.service';
import { mapToUltramsgData, wrapUltramsgPayload, type OpenWaMessageData } from './ultramsg-payload';

const PLUGIN_ID = 'ultramsg-compat';

const MESSAGE_EVENTS = new Set(['message.received', 'message.sent']);

type WebhookBeforeData = {
  sessionId?: string;
  event?: string;
  payload?: WebhookPayload;
};

function compatEnabled(): boolean {
  return process.env.ULTRAMSG_COMPAT === 'true';
}

@Injectable()
export class UltramsgCompatService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = createLogger('UltramsgCompatService');
  private readonly hookIds: string[] = [];

  constructor(
    private readonly hookManager: HookManager,
    private readonly media: UltramsgMediaService,
    private readonly sessions: SessionService,
    private readonly webhooks: WebhookService,
  ) {}

  onModuleInit(): void {
    if (!compatEnabled()) {
      this.logger.debug('ULTRAMSG_COMPAT is off; inbound reshape and auto-webhook are idle');
      return;
    }

    this.hookIds.push(this.hookManager.register(PLUGIN_ID, 'webhook:before', ctx => this.onWebhookBefore(ctx), 50));
    this.hookIds.push(this.hookManager.register(PLUGIN_ID, 'session:created', ctx => this.onSessionCreated(ctx), 50));
    this.hookIds.push(this.hookManager.register(PLUGIN_ID, 'session:ready', ctx => this.onSessionReady(ctx), 50));

    void this.ensureExistingSessionWebhooks();
  }

  onModuleDestroy(): void {
    for (const id of this.hookIds) this.hookManager.unregister(id);
    this.hookIds.length = 0;
  }

  private async onWebhookBefore(ctx: HookContext): Promise<HookResult> {
    const hookData = (ctx.data ?? {}) as WebhookBeforeData;
    const event = hookData.event;
    const payload = hookData.payload;
    if (!event || !payload || !MESSAGE_EVENTS.has(event)) {
      return { continue: true, data: hookData };
    }

    const src = (payload.data ?? {}) as OpenWaMessageData;
    const mediaUrl = this.media.saveInbound(src.media);
    const mapped = mapToUltramsgData(src, mediaUrl);
    const instanceId = await this.resolveInstanceId(hookData.sessionId);
    const envelope = wrapUltramsgPayload(event, instanceId, mapped);

    const nextPayload = payload as WebhookPayload & Record<string, unknown>;
    nextPayload.event_type = envelope.event_type;
    nextPayload.instanceId = envelope.instanceId;
    nextPayload.id = envelope.id;
    nextPayload.referenceId = envelope.referenceId;
    nextPayload.hash = envelope.hash;
    nextPayload.data = mapped as unknown as Record<string, unknown>;

    return { continue: true, data: { ...hookData, payload: nextPayload } };
  }

  private async onSessionCreated(ctx: HookContext): Promise<HookResult> {
    const data = (ctx.data ?? {}) as { id?: string };
    if (data.id) await this.ensureWebhook(data.id);
    return { continue: true, data: ctx.data };
  }

  private async onSessionReady(ctx: HookContext): Promise<HookResult> {
    if (ctx.sessionId) await this.ensureWebhook(ctx.sessionId);
    return { continue: true, data: ctx.data };
  }

  private async resolveInstanceId(sessionId: string | undefined): Promise<string> {
    if (!sessionId) return '';
    try {
      const session = await this.sessions.findOne(sessionId);
      return session.name || sessionId;
    } catch {
      return sessionId;
    }
  }

  private webhookUrl(): string {
    return (process.env.ULTRAMSG_WEBHOOK_URL ?? '').trim();
  }

  private async ensureExistingSessionWebhooks(): Promise<void> {
    const url = this.webhookUrl();
    if (!url) return;
    try {
      const sessions = await this.sessions.findAll();
      for (const session of sessions) {
        await this.ensureWebhook(session.id);
      }
    } catch (error) {
      this.logger.warn(
        `Could not auto-register UltraMsg webhooks on boot: ${error instanceof Error ? error.message : 'unknown error'}`,
      );
    }
  }

  private async ensureWebhook(sessionId: string): Promise<void> {
    const url = this.webhookUrl();
    if (!url) return;
    try {
      const existing = await this.webhooks.findBySession(sessionId);
      // Same PHP target under localhost vs public IP would double-process every inbound message.
      if (existing.some(row => this.sameWebhookTarget(row.url, url))) return;
      await this.webhooks.create(sessionId, {
        url,
        events: ['message.received'],
        retryCount: 0,
      });
      this.logger.log(`Registered list webhook for session ${sessionId}`, {
        sessionId,
        action: 'ultramsg_webhook_register',
      });
    } catch (error) {
      this.logger.warn(
        `Failed to register list webhook for ${sessionId}: ${error instanceof Error ? error.message : 'unknown error'}`,
        { sessionId, action: 'ultramsg_webhook_register_failed' },
      );
    }
  }

  /** Treat host variants of the same path as one webhook (avoids triple replies). */
  private sameWebhookTarget(a: string, b: string): boolean {
    if (a === b) return true;
    try {
      const ua = new URL(a);
      const ub = new URL(b);
      return ua.pathname.replace(/\/+$/, '') === ub.pathname.replace(/\/+$/, '');
    } catch {
      return false;
    }
  }
}
