import { Injectable, UnauthorizedException } from '@nestjs/common';
import { NotificationChannel, Prisma } from '@prisma/client';
import { PrismaService } from '../database/prisma.service';

type WascriptConfig = {
  enabled: boolean;
  instanceToken: string;
  baseUrl: string;
  fallbackToWeb: boolean;
  webhookEnabled: boolean;
  webhookSecret: string;
  webhookCallbackUrl: string;
};

type GatewaySettingsRecord = Record<string, unknown>;

const defaultWascriptConfig: WascriptConfig = {
  enabled: false,
  instanceToken: '',
  baseUrl: 'https://api-whatsapp.wascript.com.br',
  fallbackToWeb: true,
  webhookEnabled: false,
  webhookSecret: '',
  webhookCallbackUrl: '',
};

@Injectable()
export class MessagingService {
  constructor(private readonly prisma: PrismaService) {}

  async getWascriptConfig(): Promise<WascriptConfig> {
    const setting = await this.prisma.setting.findUnique({
      where: { key: 'payment_gateway_settings' },
    });
    const value = (setting?.value as GatewaySettingsRecord | null) ?? {};

    return {
      enabled: Boolean(value.wascriptEnabled ?? defaultWascriptConfig.enabled),
      instanceToken: String(value.wascriptInstanceToken ?? defaultWascriptConfig.instanceToken).trim(),
      baseUrl: String(value.wascriptBaseUrl ?? defaultWascriptConfig.baseUrl).trim() || defaultWascriptConfig.baseUrl,
      fallbackToWeb: Boolean(value.wascriptFallbackToWeb ?? defaultWascriptConfig.fallbackToWeb),
      webhookEnabled: Boolean(value.wascriptWebhookEnabled ?? defaultWascriptConfig.webhookEnabled),
      webhookSecret: String(value.wascriptWebhookSecret ?? defaultWascriptConfig.webhookSecret).trim(),
      webhookCallbackUrl: String(value.wascriptWebhookCallbackUrl ?? defaultWascriptConfig.webhookCallbackUrl).trim(),
    };
  }

  async getWascriptProviderStatus() {
    const config = await this.getWascriptConfig();
    return {
      provider: 'Wascript',
      mode: config.enabled && config.instanceToken ? 'ready' : 'pending',
      publicKeyConfigured: false,
      secretConfigured: Boolean(config.instanceToken),
      webhookConfigured: config.webhookEnabled && Boolean(config.webhookCallbackUrl || config.webhookSecret),
    } as const;
  }

  async sendWascriptText(phone: string, message: string) {
    const config = await this.getWascriptConfig();
    if (!config.enabled || !config.instanceToken) {
      throw new Error('Wascript nao configurada.');
    }

    const normalizedPhone = this.normalizePhone(phone);
    if (!normalizedPhone) {
      throw new Error('Telefone invalido para envio via Wascript.');
    }

    const text = message.trim();
    if (!text) {
      throw new Error('Mensagem vazia para envio via Wascript.');
    }

    return this.wascriptApiJsonRequest(config, `api/enviar-texto/${encodeURIComponent(config.instanceToken)}`, {
      phone: normalizedPhone,
      message: text,
    });
  }

  async handleWascriptWebhook(
    payload: Record<string, unknown>,
    headers: Record<string, string | string[] | undefined>,
    query: Record<string, string | string[] | undefined> = {},
  ) {
    const config = await this.getWascriptConfig();

    if (!this.verifyWascriptSecret(config, headers, query, payload)) {
      await this.appendAudit('wascript.invalid_secret', payload, null);
      throw new UnauthorizedException('invalid_secret');
    }

    if (!config.webhookEnabled) {
      await this.appendAudit('wascript.ignored_disabled', payload, null);
      return { status: 'ignored', reason: 'webhook_disabled' };
    }

    if (this.webhookFromMe(payload)) {
      await this.appendAudit('wascript.ignored_outgoing', payload, {
        phone: this.webhookPhone(payload),
      });
      return { status: 'ignored', reason: 'outgoing_message' };
    }

    const phone = this.webhookPhone(payload);
    const text = this.webhookMessageText(payload);
    const admins = await this.prisma.user.findMany({
      where: { role: { in: ['ADMIN', 'SUPER_ADMIN'] } },
      select: { id: true },
      take: 10,
    });

    if (admins.length) {
      await this.prisma.notification.createMany({
        data: admins.map((admin) => ({
          userId: admin.id,
          title: 'WhatsApp recebido',
          body: `${phone || 'sem telefone'}: ${text}`,
          channel: NotificationChannel.IN_APP,
          payload: JSON.parse(
            JSON.stringify({
              provider: 'wascript',
              phone,
              text,
              raw: payload,
            }),
          ) as Prisma.InputJsonValue,
        })),
      });
    }

    await this.appendAudit('wascript.message_received', payload, {
      phone,
      text,
      adminsNotified: admins.length,
    });

    return {
      status: 'received',
      provider: 'wascript',
      stats: {
        messages: 1,
        adminsNotified: admins.length,
      },
    };
  }

  private async wascriptApiJsonRequest(config: WascriptConfig, endpoint: string, body: Record<string, unknown>) {
    const response = await fetch(`${config.baseUrl.replace(/\/+$/, '')}/${endpoint.replace(/^\/+/, '')}`, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    });

    const payload = await response.json().catch(() => null);
    if (!response.ok) {
      throw new Error(
        payload && typeof payload === 'object'
          ? JSON.stringify(payload)
          : `Wascript recusou o envio com HTTP ${response.status}.`,
      );
    }

    return payload ?? { ok: true };
  }

  private verifyWascriptSecret(
    config: WascriptConfig,
    headers: Record<string, string | string[] | undefined>,
    query: Record<string, string | string[] | undefined>,
    payload: Record<string, unknown>,
  ) {
    if (!config.webhookSecret) {
      return true;
    }

    const candidates = [
      headers['x-wascript-secret'],
      headers['x-webhook-secret'],
      query.secret,
      typeof payload.secret === 'string' ? payload.secret : undefined,
    ]
      .flatMap((value) => (Array.isArray(value) ? value : [value]))
      .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
      .map((value) => value.trim());

    return candidates.some((value) => value === config.webhookSecret);
  }

  private webhookScalarByKeys(payload: Record<string, unknown>, keys: string[], maxDepth = 6): string | null {
    for (const key of keys) {
      if (Object.prototype.hasOwnProperty.call(payload, key) && !Array.isArray(payload[key])) {
        const value = String(payload[key] ?? '').trim();
        if (value) {
          return value;
        }
      }
    }

    if (maxDepth <= 0) {
      return null;
    }

    for (const value of Object.values(payload)) {
      if (!value || typeof value !== 'object' || Array.isArray(value)) {
        continue;
      }
      const found = this.webhookScalarByKeys(value as Record<string, unknown>, keys, maxDepth - 1);
      if (found) {
        return found;
      }
    }

    return null;
  }

  private webhookMessageText(payload: Record<string, unknown>) {
    return (
      this.webhookScalarByKeys(payload, [
        'text',
        'body',
        'message',
        'conversation',
        'content',
        'caption',
        'description',
        'title',
        'selectedDisplayText',
        'displayText',
      ]) ??
      `[${this.webhookScalarByKeys(payload, ['type', 'messageType', 'event']) ?? 'mensagem_wascript'}]`
    );
  }

  private webhookPhone(payload: Record<string, unknown>) {
    return this.normalizePhone(
      this.webhookScalarByKeys(payload, [
        'phone',
        'mobile',
        'number',
        'from',
        'sender',
        'sender_phone',
        'remoteJid',
        'author',
        'chatId',
        'jid',
      ]) ?? '',
    );
  }

  private webhookFromMe(payload: Record<string, unknown>) {
    const value = (this.webhookScalarByKeys(payload, ['fromMe', 'isFromMe', 'owner']) ?? '').toLowerCase();
    return ['1', 'true', 'yes', 'sim'].includes(value);
  }

  private normalizePhone(phone: string | null | undefined) {
    return String(phone ?? '').replace(/\D+/g, '').trim();
  }

  private async appendAudit(action: string, payload: Record<string, unknown>, metadata: Record<string, unknown> | null) {
    await this.prisma.auditLog.create({
      data: {
        action,
        entity: 'WASCRIPT_WEBHOOK',
        metadata: JSON.parse(
          JSON.stringify({
            ...(metadata ?? {}),
            payload,
          }),
        ) as Prisma.InputJsonValue,
      },
    });
  }
}
