import { Injectable, HttpStatus } from '@nestjs/common';
import { Logger } from 'pino-nestjs';
import axios from 'axios';
import { ClientErrorReportDto } from './errors.dto';
import { ConfigService } from '@nestjs/config';
import { AppConfig } from '../../config/configs';

const DISCORD_PRIORITY_TAGS = new Set([
  'critical',
  'fatal',
  'security',
  'payment',
  'billing',
  'subscription',
  'purchase',
  'crash',
]);

@Injectable()
export class ErrorsService {
  constructor(
    private readonly logger: Logger,
    private readonly configService: ConfigService<AppConfig, true>,
  ) {}

  private isAndroidPurchaseLog(data: any): boolean {
    const page = String(data?.page || '').toLowerCase();
    const platform = String(data?.deviceInfo?.platform || data?.error?.data?.platform || '').toLowerCase();
    const message = String(data?.error?.message || data?.error || '').toLowerCase();

    const purchasePage = page.includes('purchase') || page.includes('billing');
    const purchaseMsg =
      message.includes('[iap]') || message.includes('purchase') || message.includes('subscription');

    return platform === 'android' && (purchasePage || purchaseMsg);
  }

  private extractErrorMessage(data: any): string {
    const raw = data?.error?.message || data?.message || data?.error || '';
    return String(raw || '');
  }

  private hasHighSignalRuntimeError(data: any): boolean {
    const message = this.extractErrorMessage(data).toLowerCase();
    return (
      message.includes('invariant violation') ||
      message.includes('undefined is not an object') ||
      message.includes('cannot read property') ||
      message.includes('native module') ||
      message.includes('out of memory') ||
      message.includes('segmentation fault') ||
      message.includes('fatal')
    );
  }

  private shouldForwardToDiscord(data: any): { forward: boolean; reason: string } {
    if (this.isAndroidPurchaseLog(data)) {
      return { forward: true, reason: 'android_purchase' };
    }

    const source = String(data?.source || '').toLowerCase();
    const page = String(data?.page || '').toLowerCase();
    const action = String(data?.action || '').toLowerCase();
    const statusCode = Number(data?.request?.statusCode || 0);
    const tags = Array.isArray(data?.tags) ? data.tags.map((tag: unknown) => String(tag || '').toLowerCase()) : [];
    const isBestEffort = !!data?.context?.extra?.bestEffort;
    const message = this.extractErrorMessage(data).toLowerCase();

    if (source.includes('crash') || page.includes('frontend-crash') || action.includes('fatal')) {
      return { forward: true, reason: 'frontend_crash' };
    }

    if (statusCode >= 500) {
      return { forward: true, reason: 'server_5xx' };
    }

    if (tags.some((tag: string) => DISCORD_PRIORITY_TAGS.has(tag))) {
      return { forward: true, reason: 'priority_tag' };
    }

    if (this.hasHighSignalRuntimeError(data)) {
      return { forward: true, reason: 'runtime_signal' };
    }

    if (
      isBestEffort ||
      message === 'an unexpected error occurred' ||
      message.includes('network request failed') ||
      message.includes('request timed out') ||
      message.includes('timeout') ||
      statusCode === 401 ||
      statusCode === 403 ||
      statusCode === 404 ||
      statusCode === 409
    ) {
      return { forward: false, reason: 'noise_or_expected' };
    }

    return { forward: false, reason: 'logger_only' };
  }

  // Mirrors Express `hookLogger(payload, "app")` — errors.service.ts is the
  // only "app" mode caller, so this always targets DISCORD_WEBHOOK_APP.
  // Requires both DISCORD_WEBHOOK and DISCORD_WEBHOOK_APP to be configured,
  // matching the (slightly quirky) Express gate exactly.
  async hookLogger(err: any): Promise<void> {
    const discordWebhook = this.configService.get('webhook.discordWebhook', { infer: true });
    const discordWebhookApp = this.configService.get('webhook.discordWebhookApp', { infer: true });
    if (!discordWebhook || !discordWebhookApp) {
      return;
    }

    const nodeEnv = this.configService.get('app.env', { infer: true });
    const data = JSON.stringify({
      embeds: [
        {
          title: `Error Log ${nodeEnv === 'production' ? 'production' : 'development'}`,
          description: `${typeof err === 'object' ? JSON.stringify(err) : err}`,
          color: 16711680,
        },
      ],
    });

    try {
      await axios({
        method: 'post',
        url: discordWebhookApp,
        headers: { 'Content-Type': 'application/json' },
        data,
      });
    } catch (error) {
      this.logger.error('[hookLogger] Discord send failed', error);
    }
  }

  async logErrorReport(data: ClientErrorReportDto, userId?: string) {
    const { error, stack, deviceInfo, page, functionName, action, source, tags, request, context, runtime, eventId } =
      data;
    const requestSummary = {
      url: request?.url || null,
      apiPath: request?.apiPath || null,
      method: request?.method || null,
      statusCode: request?.statusCode || null,
      requestId: request?.requestId || null,
    };
    const tagsSummary = Array.isArray(tags) ? tags.join(', ') : '';

    this.logger.error(
      `[ClientErrorReport] User: ${userId || 'Anonymous'}\nPage: ${page || 'unknown'}\nFunction: ${
        functionName || 'unknown'
      }\nAction: ${action || 'unknown'}\nSource: ${source || 'frontend'}\nTags: ${
        tagsSummary || '-'
      }\nRequest: ${JSON.stringify(requestSummary)}\nError: ${
        typeof error === 'object' ? JSON.stringify(error) : String(error)
      }\nStack: ${stack}\nRuntime: ${JSON.stringify(runtime || {})}\nDevice: ${JSON.stringify(
        deviceInfo || {},
      )}\nContext: ${JSON.stringify(context || {})}`,
    );

    let discordPayload: Record<string, unknown> = {
      source: source || 'frontend',
      user: userId || 'Anonymous',
      page: page || 'unknown',
      functionName: functionName || null,
      action: action || null,
      tags: tags || [],
      request: requestSummary,
      context: context || {},
      runtime: runtime || {},
      eventId: eventId || null,
      error,
      stack,
      deviceInfo,
      ts: data.ts || new Date().toISOString(),
    };

    if (this.isAndroidPurchaseLog(data)) {
      discordPayload = { ...discordPayload, source: 'android_purchase' };
    }

    const discordRoute = this.shouldForwardToDiscord(data);
    this.logger.log('[Client Error] route: discord_routing_decision', {
      eventId: eventId || null,
      forwardToDiscord: discordRoute.forward,
      reason: discordRoute.reason,
      page: page || 'unknown',
      action: action || 'unknown',
      source: source || 'frontend',
      statusCode: requestSummary.statusCode,
      tags: tags || [],
    });

    if (discordRoute.forward) {
      try {
        await this.hookLogger({ ...discordPayload, routeReason: discordRoute.reason });
      } catch (hookErr) {
        this.logger.error('[Client Error] route: hookLogger_failed', hookErr);
      }
    }

    return {
      status: true,
      statusCode: HttpStatus.NO_CONTENT,
    };
  }
}
