import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { cert, deleteApp, getApps, initializeApp } from 'firebase-admin/app';
import { getMessaging } from 'firebase-admin/messaging';
import { SettingsService } from '../domain/settings.service';
import { PushDeviceService } from '../domain/push-device.service';

export interface FirebasePushPayload {
  userId: string;
  type: string;
  title: string;
  message: string;
  payload?: Record<string, unknown>;
}

const SKIP_PUSH_TYPES = new Set(['otp_code']);

@Injectable()
export class FirebasePushService {
  private readonly logger = new Logger(FirebasePushService.name);
  private initializedProjectId: string | null = null;

  constructor(
    private readonly settings: SettingsService,
    private readonly pushDevices: PushDeviceService
  ) {}

  async sendToUser(notification: FirebasePushPayload) {
    if (SKIP_PUSH_TYPES.has(notification.type)) {
      return { sentCount: 0, skipped: true };
    }

    const config = await this.loadServerConfig();
    if (!config) {
      return { sentCount: 0, skipped: true };
    }

    const tokens = await this.pushDevices.listTokensForUser(notification.userId);
    if (!tokens.length) {
      return { sentCount: 0, skipped: true };
    }

    await this.ensureApp(config);

    const data: Record<string, string> = {
      type: notification.type,
      title: notification.title,
      message: notification.message
    };

    if (notification.payload) {
      for (const [key, value] of Object.entries(notification.payload)) {
        if (value === undefined || value === null) continue;
        data[key] = typeof value === 'string' ? value : JSON.stringify(value);
      }
    }

    const messaging = getMessaging();
    let sentCount = 0;
    const staleTokens: string[] = [];

    const chunkSize = 500;
    for (let offset = 0; offset < tokens.length; offset += chunkSize) {
      const chunk = tokens.slice(offset, offset + chunkSize);
      const response = await messaging.sendEachForMulticast({
        tokens: chunk.map((item) => item.token),
        notification: {
          title: notification.title,
          body: notification.message
        },
        data,
        webpush: {
          fcmOptions: {
            link: '/'
          }
        }
      });

      sentCount += response.successCount;

      response.responses.forEach((item, index) => {
        if (item.success) return;
        const code = item.error?.code ?? '';
        if (
          code === 'messaging/registration-token-not-registered' ||
          code === 'messaging/invalid-registration-token' ||
          code === 'messaging/invalid-argument'
        ) {
          staleTokens.push(chunk[index].token);
        } else if (item.error) {
          this.logger.warn(`FCM ошибка для токена: ${item.error.message}`);
        }
      });
    }

    if (staleTokens.length) {
      await this.pushDevices.removeTokens(staleTokens);
      this.logger.log(`Удалено устаревших FCM-токенов: ${staleTokens.length}`);
    }

    return { sentCount, skipped: false };
  }

  async sendTestToUser(userId: string) {
    const config = await this.loadServerConfig();
    if (!config) {
      throw new BadRequestException('Firebase push не настроен или отключён');
    }

    const tokens = await this.pushDevices.listTokensForUser(userId);
    if (!tokens.length) {
      throw new BadRequestException('У вас нет зарегистрированных устройств для push. Откройте IdP в браузере и разрешите уведомления.');
    }

    const result = await this.sendToUser({
      userId,
      type: 'system_test',
      title: 'Тест Firebase Push',
      message: 'Если вы видите это уведомление — FCM настроен корректно.',
      payload: { source: 'admin_test' }
    });

    if (!result.sentCount) {
      throw new BadRequestException('Не удалось отправить тестовое push-уведомление. Проверьте ключи сервисного аккаунта.');
    }

    return {
      message: `Тестовое push отправлено на ${result.sentCount} устройств(а)`,
      sentCount: result.sentCount
    };
  }

  private async loadServerConfig() {
    const enabled = await this.settings.getBoolean('FIREBASE_PUSH_ENABLED', false);
    if (!enabled) return null;

    const projectId = (await this.settings.getValue('FIREBASE_PROJECT_ID', '')).trim();
    const clientEmail = (await this.settings.getValue('FIREBASE_CLIENT_EMAIL', '')).trim();
    const privateKeyRaw = await this.settings.getValue('FIREBASE_PRIVATE_KEY', '');
    const privateKey = this.normalizePrivateKey(privateKeyRaw);

    if (!projectId || !clientEmail || !privateKey) {
      this.logger.warn('Firebase push включён, но не заполнены project_id, client_email или private_key');
      return null;
    }

    return { projectId, clientEmail, privateKey };
  }

  private normalizePrivateKey(value: string) {
    const trimmed = value.trim();
    if (!trimmed) return '';
    return trimmed.replace(/\\n/g, '\n');
  }

  private async ensureApp(config: { projectId: string; clientEmail: string; privateKey: string }) {
    if (this.initializedProjectId === config.projectId && getApps().length) {
      return;
    }

    for (const app of getApps()) {
      await deleteApp(app);
    }

    initializeApp({
      credential: cert({
        projectId: config.projectId,
        clientEmail: config.clientEmail,
        privateKey: config.privateKey
      }),
      projectId: config.projectId
    });

    this.initializedProjectId = config.projectId;
  }
}
