import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as webPush from 'web-push';
import { PushSubscription } from '../../database/entities/push-subscription.entity';
import { ConfigService } from '@nestjs/config';
import { UserPreferencesService } from '../users/user-preferences.service';

export interface PushSubscriptionData {
  endpoint: string;
  keys: {
    p256dh: string;
    auth: string;
  };
}

export interface PushNotificationPayload {
  title: string;
  body: string;
  icon?: string;
  badge?: string;
  tag?: string;
  data?: any;
  requireInteraction?: boolean;
}

@Injectable()
export class PushService {
  private readonly logger = new Logger('PushService');

  constructor(
    @InjectRepository(PushSubscription)
    private pushSubscriptionRepository: Repository<PushSubscription>,
    private configService: ConfigService,
    private userPreferencesService: UserPreferencesService,
  ) {
    // Configure web-push with VAPID keys
    const vapidPublicKey = this.configService.get<string>('VAPID_PUBLIC_KEY');
    const vapidPrivateKey = this.configService.get<string>(
      'VAPID_PRIVATE_KEY',
    );
    const vapidSubject = this.configService.get<string>('VAPID_SUBJECT');

    if (vapidPublicKey && vapidPrivateKey && vapidSubject) {
      webPush.setVapidDetails(vapidSubject, vapidPublicKey, vapidPrivateKey);
      this.logger.log('Web Push configured with VAPID keys');
    } else {
      this.logger.warn(
        'VAPID keys not configured. Push notifications will not work.',
      );
    }
  }

  /**
   * Get the public VAPID key for frontend subscription
   */
  getPublicVapidKey(): string {
    return this.configService.get<string>('VAPID_PUBLIC_KEY') || '';
  }

  /**
   * Subscribe a user to push notifications
   */
  async subscribe(
    userId: string,
    subscriptionData: PushSubscriptionData,
    userAgent?: string,
  ): Promise<PushSubscription> {
    // Parse user agent for device info
    const deviceInfo = this.parseUserAgent(userAgent);

    // Check if subscription already exists
    const existing = await this.pushSubscriptionRepository.findOne({
      where: { endpoint: subscriptionData.endpoint },
    });

    if (existing) {
      // Update existing subscription
      existing.userId = userId;
      existing.p256dh = subscriptionData.keys.p256dh;
      existing.auth = subscriptionData.keys.auth;
      existing.userAgent = userAgent || null;
      existing.deviceType = deviceInfo.deviceType;
      existing.browser = deviceInfo.browser;
      existing.isActive = true;
      existing.failedAttempts = 0;
      existing.lastError = null;

      const updated = await this.pushSubscriptionRepository.save(existing);
      this.logger.log(`Updated push subscription for user ${userId}`);
      return updated;
    }

    // Create new subscription
    const subscription = this.pushSubscriptionRepository.create({
      userId,
      endpoint: subscriptionData.endpoint,
      p256dh: subscriptionData.keys.p256dh,
      auth: subscriptionData.keys.auth,
      userAgent: userAgent || null,
      deviceType: deviceInfo.deviceType,
      browser: deviceInfo.browser,
      isActive: true,
      failedAttempts: 0,
    });

    const saved = await this.pushSubscriptionRepository.save(subscription);
    this.logger.log(`Created new push subscription for user ${userId}`);
    return saved;
  }

  /**
   * Unsubscribe from push notifications
   */
  async unsubscribe(userId: string, endpoint: string): Promise<void> {
    const subscription = await this.pushSubscriptionRepository.findOne({
      where: { userId, endpoint },
    });

    if (subscription) {
      await this.pushSubscriptionRepository.delete(subscription.id);
      this.logger.log(
        `Deleted push subscription for user ${userId}, endpoint ${endpoint.substring(0, 50)}...`,
      );
    }
  }

  /**
   * Get all active subscriptions for a user
   */
  async getUserSubscriptions(userId: string): Promise<PushSubscription[]> {
    return this.pushSubscriptionRepository.find({
      where: { userId, isActive: true },
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * Send a push notification to a specific user
   */
  async sendToUser(
    userId: string,
    payload: PushNotificationPayload,
  ): Promise<{ sent: number; failed: number }> {
    // Check if user has push notifications enabled
    const isPushEnabled =
      await this.userPreferencesService.isPushNotificationsEnabled(userId);

    if (!isPushEnabled) {
      this.logger.debug(
        `Push notifications disabled for user ${userId}, skipping`,
      );
      return { sent: 0, failed: 0 };
    }

    const subscriptions = await this.getUserSubscriptions(userId);

    if (subscriptions.length === 0) {
      this.logger.debug(`No active subscriptions for user ${userId}`);
      return { sent: 0, failed: 0 };
    }

    this.logger.debug(
      `Sending push notification to ${subscriptions.length} subscriptions for user ${userId}`,
    );

    const results = await Promise.allSettled(
      subscriptions.map((sub) => this.sendToSubscription(sub, payload)),
    );

    const sent = results.filter((r) => r.status === 'fulfilled').length;
    const failed = results.filter((r) => r.status === 'rejected').length;

    this.logger.log(
      `Push notification sent to user ${userId}: ${sent} sent, ${failed} failed`,
    );

    return { sent, failed };
  }

  /**
   * Send a push notification to a specific subscription
   */
  private async sendToSubscription(
    subscription: PushSubscription,
    payload: PushNotificationPayload,
  ): Promise<void> {
    const pushSubscription: webPush.PushSubscription = {
      endpoint: subscription.endpoint,
      keys: {
        p256dh: subscription.p256dh,
        auth: subscription.auth,
      },
    };

    try {
      await webPush.sendNotification(
        pushSubscription,
        JSON.stringify(payload),
        {
          TTL: 3600, // 1 hour
        },
      );

      // Update success status
      await this.pushSubscriptionRepository.update(subscription.id, {
        lastSuccessAt: new Date(),
        failedAttempts: 0,
        lastError: null,
      });

      this.logger.debug(
        `Successfully sent push to subscription ${subscription.id}`,
      );
    } catch (error: any) {
      this.logger.error(
        `Failed to send push to subscription ${subscription.id}: ${error.message}`,
      );

      // Handle specific error codes
      const statusCode = error.statusCode;

      if (statusCode === 404 || statusCode === 410) {
        // Subscription no longer valid - deactivate it
        await this.pushSubscriptionRepository.update(subscription.id, {
          isActive: false,
          lastError: `Endpoint returned ${statusCode}`,
        });
        this.logger.log(
          `Deactivated invalid subscription ${subscription.id} (${statusCode})`,
        );
      } else {
        // Temporary error - increment failure count
        const failedAttempts = (subscription.failedAttempts || 0) + 1;
        const isActive = failedAttempts < 5; // Deactivate after 5 failures

        await this.pushSubscriptionRepository.update(subscription.id, {
          failedAttempts,
          isActive,
          lastError: error.message,
        });

        if (!isActive) {
          this.logger.warn(
            `Deactivated subscription ${subscription.id} after ${failedAttempts} failures`,
          );
        }
      }

      throw error;
    }
  }

  /**
   * Send a push notification to multiple users
   */
  async sendToUsers(
    userIds: string[],
    payload: PushNotificationPayload,
  ): Promise<{ sent: number; failed: number }> {
    const results = await Promise.allSettled(
      userIds.map((userId) => this.sendToUser(userId, payload)),
    );

    const totals = results.reduce(
      (acc, result) => {
        if (result.status === 'fulfilled') {
          acc.sent += result.value.sent;
          acc.failed += result.value.failed;
        }
        return acc;
      },
      { sent: 0, failed: 0 },
    );

    this.logger.log(
      `Bulk push notification: ${totals.sent} sent, ${totals.failed} failed to ${userIds.length} users`,
    );

    return totals;
  }

  /**
   * Test push notification to verify subscription works
   */
  async sendTestNotification(userId: string): Promise<void> {
    await this.sendToUser(userId, {
      title: 'ParentFlow - Test Notification',
      body: 'Push notifications are working! You will receive updates here.',
      icon: '/icons/app-icon-192.png',
      badge: '/icons/badge-72.png',
      tag: 'test-notification',
      data: { url: '/' },
    });
  }

  /**
   * Parse user agent to extract device and browser info
   */
  private parseUserAgent(
    userAgent?: string,
  ): { deviceType: string | null; browser: string | null } {
    if (!userAgent) {
      return { deviceType: null, browser: null };
    }

    const ua = userAgent.toLowerCase();

    // Detect device type
    let deviceType: string | null = 'desktop';
    if (ua.includes('mobile') || ua.includes('android')) {
      deviceType = 'mobile';
    } else if (ua.includes('tablet') || ua.includes('ipad')) {
      deviceType = 'tablet';
    }

    // Detect browser
    let browser: string | null = null;
    if (ua.includes('chrome')) browser = 'chrome';
    else if (ua.includes('firefox')) browser = 'firefox';
    else if (ua.includes('safari')) browser = 'safari';
    else if (ua.includes('edge')) browser = 'edge';
    else if (ua.includes('opera')) browser = 'opera';

    return { deviceType, browser };
  }

  /**
   * Clean up old inactive subscriptions
   */
  async cleanupInactiveSubscriptions(daysOld: number = 90): Promise<number> {
    const cutoffDate = new Date(Date.now() - daysOld * 24 * 60 * 60 * 1000);

    const result = await this.pushSubscriptionRepository
      .createQueryBuilder()
      .delete()
      .where('is_active = false')
      .andWhere('updated_at < :cutoffDate', { cutoffDate })
      .execute();

    const deleted = result.affected || 0;
    this.logger.log(
      `Cleaned up ${deleted} inactive push subscriptions (older than ${daysOld} days)`,
    );

    return deleted;
  }

  /**
   * Get push notification statistics
   */
  async getStatistics(userId?: string): Promise<{
    totalSubscriptions: number;
    activeSubscriptions: number;
    inactiveSubscriptions: number;
    byBrowser: Record<string, number>;
    byDeviceType: Record<string, number>;
  }> {
    const query = this.pushSubscriptionRepository.createQueryBuilder('sub');

    if (userId) {
      query.where('sub.userId = :userId', { userId });
    }

    const subscriptions = await query.getMany();

    const stats = {
      totalSubscriptions: subscriptions.length,
      activeSubscriptions: subscriptions.filter((s) => s.isActive).length,
      inactiveSubscriptions: subscriptions.filter((s) => !s.isActive).length,
      byBrowser: {} as Record<string, number>,
      byDeviceType: {} as Record<string, number>,
    };

    subscriptions.forEach((sub) => {
      if (sub.browser) {
        stats.byBrowser[sub.browser] = (stats.byBrowser[sub.browser] || 0) + 1;
      }
      if (sub.deviceType) {
        stats.byDeviceType[sub.deviceType] =
          (stats.byDeviceType[sub.deviceType] || 0) + 1;
      }
    });

    return stats;
  }
}
