import axios from 'axios';
import { INotificationGateway } from '../../application/ports/notification-gateway';
import { ILogger } from '../../application/ports/logger';
import { IDeviceRepository } from '../../domain/repositories/device-repository';
import {
  TransactionNotification,
  TransactionPushNotification,
} from '../../application/services/transaction-notification.service';

interface OneSignalNotificationPayload {
  app_id: string;
  include_player_ids: string[];
  headings: Record<string, string>;
  contents: Record<string, string>;
  data: Record<string, unknown>;
  priority: number;
  android_visibility: number;
  ios_badgeType: string;
  ios_badgeCount: number;
  small_icon?: string;
  accent_color?: string;
}

const toArgbHex = (color: string): string => {
  const hex = color.replace('#', '');
  if (hex.length === 6) {
    return `FF${hex.toUpperCase()}`;
  }
  return hex.toUpperCase();
};

const isPushNotification = (message: unknown): message is TransactionPushNotification => {
  const candidate = message as Partial<TransactionPushNotification>;
  return (
    typeof candidate === 'object' &&
    candidate !== null &&
    typeof candidate.recipient === 'object' &&
    candidate.recipient !== null &&
    typeof candidate.amount === 'object' &&
    candidate.amount !== null
  );
};

/**
 * OneSignal REST API implementation of the notification gateway.
 *
 * Translates internal device IDs into OneSignal player IDs (push tokens)
 * stored on the device record and sends push notifications via the
 * OneSignal `/notifications` endpoint.
 */
export class OneSignalPushGateway implements INotificationGateway {
  constructor(
    private readonly appId: string,
    private readonly restApiKey: string,
    private readonly deviceRepository: IDeviceRepository,
    private readonly logger: ILogger,
  ) {}

  async sendToDevice(deviceId: string, message: unknown): Promise<void> {
    const notification = message as TransactionNotification;

    const device = await this.deviceRepository.findById(deviceId);
    if (!device?.pushToken) {
      this.logger.debug('[OneSignal] Skipping push: device has no push token', {
        deviceId,
      });
      return;
    }

    const payload = isPushNotification(message)
      ? this.buildStyledPayload(device.pushToken, message)
      : this.buildFallbackPayload(device.pushToken, notification);

    this.logger.debug('[OneSignal] Sending push notification', {
      deviceId,
      pushToken: device.pushToken,
      transactionId: notification.transaction.id,
    });

    try {
      await axios.post(
        'https://api.onesignal.com/notifications',
        payload,
        {
          headers: {
            Authorization: `Basic ${this.restApiKey}`,
            'Content-Type': 'application/json',
          },
        },
      );
      this.logger.debug('[OneSignal] Push notification sent', {
        deviceId,
        transactionId: notification.transaction.id,
      });
    } catch (error) {
      this.logger.error('[OneSignal] Failed to send push notification', {
        deviceId,
        pushToken: device.pushToken,
        transactionId: notification.transaction.id,
        error,
      });
    }
  }

  isDeviceConnected(_deviceId: string): boolean {
    // Push notifications are always reachable from the server perspective.
    // The presence of a push token is checked in sendToDevice.
    return true;
  }

  private buildStyledPayload(
    pushToken: string,
    notification: TransactionPushNotification,
  ): OneSignalNotificationPayload {
    const { recipient, amount } = notification;
    const counterparty = recipient.counterpartyAddress;

    let title: string;
    let body: string;

    if (recipient.isSender) {
      title = `💸 Sent: ${amount.formatted} ${amount.symbol}`;
      body = counterparty ? `To ${this.shortenAddress(counterparty)}` : 'To external address';
    } else if (recipient.isReceiver) {
      title = `💰 Received: ${amount.formatted} ${amount.symbol}`;
      body = counterparty ? `From ${this.shortenAddress(counterparty)}` : 'From external address';
    } else {
      title = 'Transaction received';
      body = `Transaction ${this.shortenAddress(notification.transaction.hash)} is confirmed`;
    }

    return {
      app_id: this.appId,
      include_player_ids: [pushToken],
      headings: { en: title },
      contents: { en: body },
      data: {
        type: notification.type,
        transactionId: notification.transaction.id,
        status: notification.transaction.status,
      },
      // Priority 10 = max; needed for Android heads-up notifications.
      priority: 10,
      android_visibility: 1,
      ios_badgeType: 'Increase',
      ios_badgeCount: 1,
      small_icon: 'notification_icon',
      accent_color: toArgbHex('#5092f8'),
    };
  }

  private buildFallbackPayload(
    pushToken: string,
    notification: TransactionNotification,
  ): OneSignalNotificationPayload {
    const transaction = notification.transaction;

    return {
      app_id: this.appId,
      include_player_ids: [pushToken],
      headings: { en: 'Transaction received' },
      contents: {
        en: `Transaction ${this.shortenAddress(transaction.hash)} is ${transaction.status}`,
      },
      data: {
        type: notification.type,
        transactionId: transaction.id,
        status: transaction.status,
      },
      priority: 10,
      android_visibility: 1,
      ios_badgeType: 'Increase',
      ios_badgeCount: 1,
      small_icon: 'notification_icon',
      accent_color: toArgbHex('#5092f8'),
    };
  }

  private shortenAddress(address: string): string {
    if (address.length <= 12) {
      return address;
    }
    return `${address.slice(0, 6)}...${address.slice(-4)}`;
  }
}
