import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { SwPush } from '@angular/service-worker';
import { Observable, firstValueFrom } from 'rxjs';
import { ApiConfigService } from './api-config.service';

interface VapidPublicKeyResponse {
  publicKey: string;
}

/**
 * Manages this device's Web Push subscription for the logged-in member: request permission,
 * subscribe with the server's VAPID key, persist the subscription on the API, and clean up on
 * opt-out. The ngsw service worker shows the notifications; here we only handle the click → route.
 */
@Injectable({ providedIn: 'root' })
export class PushNotificationService {
  private readonly swPush = inject(SwPush);
  private readonly http = inject(HttpClient);
  private readonly api = inject(ApiConfigService);
  private readonly router = inject(Router);

  /** Emits the current subscription (or null). Lets the UI reflect enabled/disabled state. */
  readonly subscription$: Observable<PushSubscription | null> = this.swPush.subscription;

  constructor() {
    // When a notification is clicked, bring the app to the route carried in its data.url.
    if (this.swPush.isEnabled) {
      this.swPush.notificationClicks.subscribe(({ notification }) => {
        const url = notification.data?.['url'] as string | undefined;
        if (url) {
          this.router.navigateByUrl(url);
        }
      });
    }
  }

  /**
   * True when the service worker is active and the browser supports push. False in dev (the ngsw
   * worker is disabled there) and on browsers/contexts without push — the UI should explain why.
   */
  get isSupported(): boolean {
    return this.swPush.isEnabled && 'PushManager' in window;
  }

  /** Whether the browser has already granted notification permission. */
  get permission(): NotificationPermission {
    return typeof Notification !== 'undefined' ? Notification.permission : 'denied';
  }

  /** Subscribe this device and store the subscription on the server. */
  async enable(): Promise<void> {
    if (!this.isSupported) {
      throw new Error('此瀏覽器或環境不支援推播（開發模式下 service worker 為停用）。');
    }

    const { publicKey } = await firstValueFrom(
      this.http.get<VapidPublicKeyResponse>(this.api.getApiUrl('push/vapid-public-key')),
    );
    if (!publicKey) {
      throw new Error('伺服器尚未設定推播金鑰 (VAPID)。');
    }

    const subscription = await this.swPush.requestSubscription({ serverPublicKey: publicKey });
    const json = subscription.toJSON();
    await firstValueFrom(
      this.http.post(this.api.getApiUrl('push/subscriptions'), {
        endpoint: json.endpoint,
        keys: { p256dh: json.keys?.['p256dh'], auth: json.keys?.['auth'] },
        userAgent: navigator.userAgent,
      }),
    );
  }

  /** Unsubscribe this device and remove the subscription from the server. */
  async disable(): Promise<void> {
    const subscription = await firstValueFrom(this.swPush.subscription);
    const endpoint = subscription?.endpoint;
    if (subscription) {
      await this.swPush.unsubscribe();
    }
    if (endpoint) {
      await firstValueFrom(
        this.http.request('delete', this.api.getApiUrl('push/subscriptions'), { body: { endpoint } }),
      );
    }
  }

  /** Ask the server to push a test notification to this member's devices. */
  async sendTestToSelf(): Promise<void> {
    await firstValueFrom(this.http.post(this.api.getApiUrl('push/test'), {}));
  }
}
