import { Injectable, type OnModuleInit } from '@nestjs/common';
import { type LoggerService } from '@modules/shared';

export interface SendZaloOaDto {
  /** Zalo user ID (follower UID from OA) */
  toUid: string;
  /** ZNS template ID registered in Zalo OA Manager */
  templateId: string;
  /** Template parameter key-value pairs */
  templateData: Record<string, string>;
}

export interface ZaloOaMessageResult {
  messageId: string;
}

const MAX_RETRIES = 3;
const BASE_DELAY_MS = 1000;

/**
 * Service for sending template-based messages via Zalo Official Account (OA) API v3.
 *
 * Uses the Zalo Notification Service (ZNS) to deliver transactional messages
 * such as new inquiry alerts, payment confirmations, and listing status changes.
 *
 * Requires ZALO_OA_ACCESS_TOKEN and ZALO_OA_ID to be configured.
 */
@Injectable()
export class ZaloOaService implements OnModuleInit {
  private oaId = '';
  private accessToken = '';
  private initialized = false;
  private readonly znsUrl = 'https://business.openapi.zalo.me/message/template';

  constructor(private readonly logger: LoggerService) {}

  onModuleInit(): void {
    this.oaId = process.env['ZALO_OA_ID'] ?? '';
    this.accessToken = process.env['ZALO_OA_ACCESS_TOKEN'] ?? '';

    if (!this.oaId || !this.accessToken) {
      this.logger.warn(
        'ZALO_OA_ID or ZALO_OA_ACCESS_TOKEN not set — Zalo OA notifications disabled',
        'ZaloOaService',
      );
      return;
    }

    this.initialized = true;
    this.logger.log(
      `Zalo OA configured for OA ID "${this.oaId}"`,
      'ZaloOaService',
    );
  }

  get isAvailable(): boolean {
    return this.initialized;
  }

  /**
   * Send a template-based message to a Zalo user via ZNS (Zalo Notification Service).
   *
   * The user must be a follower of the Official Account, and the template must be
   * pre-registered and approved in the Zalo OA Manager console.
   */
  async sendMessage(dto: SendZaloOaDto): Promise<ZaloOaMessageResult> {
    return this.sendWithRetry(dto);
  }

  private async sendWithRetry(dto: SendZaloOaDto): Promise<ZaloOaMessageResult> {
    if (!this.initialized) {
      throw new Error('Zalo OA not initialized — ZALO_OA_ID / ZALO_OA_ACCESS_TOKEN not configured');
    }

    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
      try {
        const result = await this.send(dto);
        return result;
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));

        if (attempt < MAX_RETRIES) {
          const delayMs = BASE_DELAY_MS * Math.pow(2, attempt - 1);
          this.logger.warn(
            `Zalo OA attempt ${attempt}/${MAX_RETRIES} failed: ${lastError.message}. Retrying in ${delayMs}ms...`,
            'ZaloOaService',
          );
          await this.delay(delayMs);
        }
      }
    }

    this.logger.error(
      `Zalo OA message failed after ${MAX_RETRIES} attempts: ${lastError?.message}`,
      'ZaloOaService',
    );
    throw lastError;
  }

  private async send(dto: SendZaloOaDto): Promise<ZaloOaMessageResult> {
    const body = {
      phone: dto.toUid,
      template_id: dto.templateId,
      template_data: dto.templateData,
    };

    const response = await fetch(this.znsUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        access_token: this.accessToken,
      },
      body: JSON.stringify(body),
    });

    if (!response.ok) {
      const errorText = await response.text().catch(() => 'Unknown error');
      throw new Error(`Zalo OA API error (${response.status}): ${errorText}`);
    }

    const data = (await response.json()) as {
      error?: number;
      message?: string;
      data?: { msg_id?: string };
    };

    // Zalo API returns error=0 on success
    if (data.error !== undefined && data.error !== 0) {
      throw new Error(
        `Zalo OA message rejected (code ${data.error}): ${data.message ?? 'Unknown reason'}`,
      );
    }

    const messageId = data.data?.msg_id ?? `zalo-oa-${Date.now()}`;

    this.logger.log(
      `Zalo OA message sent to ${dto.toUid.slice(0, 6)}***: ${messageId}`,
      'ZaloOaService',
    );

    return { messageId };
  }

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