/** ConsentManager - DSGVO/TTDSG-konformes Consent Management fuer Web, PWA und Mobile. */

import type {
  ConsentConfig,
  ConsentState,
  ConsentCategory,
  ConsentInput,
  ConsentEventType,
  ConsentEventCallback,
  ConsentEventData,
} from '../types';
import { ConsentStorage } from './ConsentStorage';
import { ScriptBlocker } from './ScriptBlocker';
import { ConsentAPI } from './ConsentAPI';
import { EventEmitter } from '../utils/EventEmitter';
import { generateFingerprint } from '../utils/fingerprint';
import { SDK_VERSION } from '../version';
import { mergeConsentConfig } from './consent-manager-config';
import { updateGoogleConsentMode as applyGoogleConsent } from './consent-manager-google';
import {
  normalizeConsentInput,
  isConsentExpired,
  needsConsent,
  ALL_CATEGORIES,
  MINIMAL_CATEGORIES,
} from './consent-manager-helpers';

export class ConsentManager {
  private config: ConsentConfig;
  private storage: ConsentStorage;
  private scriptBlocker: ScriptBlocker;
  private api: ConsentAPI;
  private events: EventEmitter<ConsentEventData>;
  private currentConsent: ConsentState | null = null;
  private initialized = false;
  private bannerVisible = false;
  private deviceFingerprint: string = '';

  constructor(config: ConsentConfig) {
    this.config = mergeConsentConfig(config);
    this.storage = new ConsentStorage(this.config);
    this.scriptBlocker = new ScriptBlocker(this.config);
    this.api = new ConsentAPI(this.config);
    this.events = new EventEmitter();

    this.log('ConsentManager created with config:', this.config);
  }

  /**
   * SDK initialisieren
   */
  async init(): Promise<void> {
    if (this.initialized) {
      this.log('Already initialized, skipping');
      return;
    }

    try {
      this.log('Initializing ConsentManager...');

      // Device Fingerprint generieren
      this.deviceFingerprint = await generateFingerprint();

      // Consent aus Storage laden
      this.currentConsent = this.storage.get();

      if (this.currentConsent) {
        this.log('Loaded consent from storage:', this.currentConsent);

        // Pruefen ob Consent abgelaufen
        if (isConsentExpired(this.currentConsent, this.config)) {
          this.log('Consent expired, clearing');
          this.storage.clear();
          this.currentConsent = null;
        } else {
          // Consent anwenden
          this.applyConsent();
        }
      }

      // Script-Blocker initialisieren
      this.scriptBlocker.init();

      this.initialized = true;
      this.emit('init', this.currentConsent);

      // Banner anzeigen falls noetig
      if (needsConsent(this.currentConsent, this.config)) {
        this.showBanner();
      }

      this.log('ConsentManager initialized successfully');
    } catch (error) {
      this.handleError(error as Error);
      throw error;
    }
  }

  // --- Public API ---

  /**
   * Pruefen ob Consent fuer Kategorie vorhanden
   */
  hasConsent(category: ConsentCategory): boolean {
    if (!this.currentConsent) {
      return category === 'essential';
    }
    return this.currentConsent.categories[category] ?? false;
  }

  /**
   * Pruefen ob Consent fuer Vendor vorhanden
   */
  hasVendorConsent(vendorId: string): boolean {
    if (!this.currentConsent) {
      return false;
    }
    return this.currentConsent.vendors[vendorId] ?? false;
  }

  /**
   * Aktuellen Consent-State abrufen
   */
  getConsent(): ConsentState | null {
    return this.currentConsent ? { ...this.currentConsent } : null;
  }

  /**
   * Consent setzen
   */
  async setConsent(input: ConsentInput): Promise<void> {
    const categories = normalizeConsentInput(input);

    // Essential ist immer aktiv
    categories.essential = true;

    const newConsent: ConsentState = {
      categories,
      vendors: 'vendors' in input && input.vendors ? input.vendors : {},
      timestamp: new Date().toISOString(),
      version: SDK_VERSION,
    };

    try {
      // An Backend senden
      const response = await this.api.saveConsent({
        siteId: this.config.siteId,
        deviceFingerprint: this.deviceFingerprint,
        consent: newConsent,
      });

      newConsent.consentId = response.consentId;
      newConsent.expiresAt = response.expiresAt;

      // Lokal speichern
      this.storage.set(newConsent);
      this.currentConsent = newConsent;

      // Consent anwenden
      this.applyConsent();

      // Event emittieren
      this.emit('change', newConsent);
      this.config.onConsentChange?.(newConsent);

      this.log('Consent saved:', newConsent);
    } catch (error) {
      // Bei Netzwerkfehler trotzdem lokal speichern
      this.log('API error, saving locally:', error);
      this.storage.set(newConsent);
      this.currentConsent = newConsent;
      this.applyConsent();
      this.emit('change', newConsent);
    }
  }

  /**
   * Alle Kategorien akzeptieren
   */
  async acceptAll(): Promise<void> {
    await this.setConsent(ALL_CATEGORIES);
    this.emit('accept_all', this.currentConsent!);
    this.hideBanner();
  }

  /**
   * Alle nicht-essentiellen Kategorien ablehnen
   */
  async rejectAll(): Promise<void> {
    await this.setConsent(MINIMAL_CATEGORIES);
    this.emit('reject_all', this.currentConsent!);
    this.hideBanner();
  }

  /**
   * Alle Einwilligungen widerrufen
   */
  async revokeAll(): Promise<void> {
    if (this.currentConsent?.consentId) {
      try {
        await this.api.revokeConsent(this.currentConsent.consentId);
      } catch (error) {
        this.log('Failed to revoke on server:', error);
      }
    }

    this.storage.clear();
    this.currentConsent = null;
    this.scriptBlocker.blockAll();

    this.log('All consents revoked');
  }

  /**
   * Consent-Daten exportieren (DSGVO Art. 20)
   */
  async exportConsent(): Promise<string> {
    const exportData = {
      currentConsent: this.currentConsent,
      exportedAt: new Date().toISOString(),
      siteId: this.config.siteId,
      deviceFingerprint: this.deviceFingerprint,
    };

    return JSON.stringify(exportData, null, 2);
  }

  // --- Banner Control ---

  /**
   * Pruefen ob Consent-Abfrage noetig
   */
  needsConsent(): boolean {
    return needsConsent(this.currentConsent, this.config);
  }

  /**
   * Banner anzeigen
   */
  showBanner(): void {
    if (this.bannerVisible) return;
    this.bannerVisible = true;
    this.emit('banner_show', undefined);
    this.config.onBannerShow?.();
    this.log('Banner shown');
  }

  /**
   * Banner verstecken
   */
  hideBanner(): void {
    if (!this.bannerVisible) return;
    this.bannerVisible = false;
    this.emit('banner_hide', undefined);
    this.config.onBannerHide?.();
    this.log('Banner hidden');
  }

  /**
   * Einstellungs-Modal oeffnen
   */
  showSettings(): void {
    this.emit('settings_open', undefined);
    this.log('Settings opened');
  }

  /**
   * Pruefen ob Banner sichtbar
   */
  isBannerVisible(): boolean {
    return this.bannerVisible;
  }

  // --- Event Handling ---

  /**
   * Event-Listener registrieren
   */
  on<T extends ConsentEventType>(
    event: T,
    callback: ConsentEventCallback<ConsentEventData[T]>
  ): () => void {
    return this.events.on(event, callback);
  }

  /**
   * Event-Listener entfernen
   */
  off<T extends ConsentEventType>(
    event: T,
    callback: ConsentEventCallback<ConsentEventData[T]>
  ): void {
    this.events.off(event, callback);
  }

  // --- Internal Methods ---

  /**
   * Consent anwenden (Skripte aktivieren/blockieren)
   */
  private applyConsent(): void {
    if (!this.currentConsent) return;

    for (const [category, allowed] of Object.entries(
      this.currentConsent.categories
    )) {
      if (allowed) {
        this.scriptBlocker.enableCategory(category as ConsentCategory);
      } else {
        this.scriptBlocker.disableCategory(category as ConsentCategory);
      }
    }

    // Google Consent Mode aktualisieren
    if (applyGoogleConsent(this.currentConsent)) {
      this.log('Google Consent Mode updated');
    }
  }

  private emit<T extends ConsentEventType>(event: T, data: ConsentEventData[T]): void {
    this.events.emit(event, data);
  }

  private handleError(error: Error): void {
    this.log('Error:', error);
    this.emit('error', error);
    this.config.onError?.(error);
  }

  private log(...args: unknown[]): void {
    if (this.config.debug) console.log('[ConsentSDK]', ...args);
  }

  // --- Static Methods ---

  /**
   * SDK-Version abrufen
   */
  static getVersion(): string {
    return SDK_VERSION;
  }
}

// Default-Export
export default ConsentManager;
