// auth.service.ts
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { FirebaseApp } from '@angular/fire/app';
import { GoogleAuthProvider, UserCredential, createUserWithEmailAndPassword, getAuth, signInWithCustomToken, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth';
import { BehaviorSubject, Observable, catchError, firstValueFrom, map, of, shareReplay, take, tap } from 'rxjs';
import { environment } from '../../environments/environment';
import { MailService } from './mail.service';

export type UserRole = 'admin' | 'pro' | 'guest';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private app = inject(FirebaseApp);
  private auth = getAuth(this.app);
  private http = inject(HttpClient);
  private mailService = inject(MailService);
  // Add a BehaviorSubject to track the current user role
  private userRoleSubject = new BehaviorSubject<UserRole | null>(null);
  public userRole$ = this.userRoleSubject.asObservable();
  // Referenz für den gecachten API-Aufruf
  private cachedUserRole$: Observable<UserRole | null> | null = null;

  // Zeitraum in ms, nach dem der Cache zurückgesetzt werden soll (z.B. 5 Minuten)
  private cacheDuration = 5 * 60 * 1000;
  private lastCacheTime = 0;
  constructor() {
    // Load role from token when service is initialized
    this.loadRoleFromToken();
  }

  private loadRoleFromToken(): void {
    this.getToken().then(token => {
      if (token) {
        const role = this.extractRoleFromToken(token);
        this.userRoleSubject.next(role);
      } else {
        this.userRoleSubject.next(null);
      }
    });
  }

  private extractRoleFromToken(token: string): UserRole | null {
    try {
      const payloadBase64 = token.split('.')[1];
      const payloadJson = atob(payloadBase64.replace(/-/g, '+').replace(/_/g, '/'));
      const payload = JSON.parse(payloadJson);
      return (payload.role as UserRole) || null;
    } catch (e) {
      return null;
    }
  }
  // Registrierung mit Email und Passwort
  async registerWithEmail(email: string, password: string): Promise<UserCredential> {
    // Bestimmen der aktuellen Umgebung/Domain für die Verifizierungs-URL
    let verificationUrl = '';

    // Prüfen der aktuellen Umgebung basierend auf dem Host
    const currentHost = window.location.hostname;

    if (currentHost.includes('localhost')) {
      verificationUrl = 'http://localhost:4200/email-authorized';
    } else if (currentHost.includes('dev.bizmatch.net')) {
      verificationUrl = 'https://dev.bizmatch.net/email-authorized';
    } else {
      verificationUrl = 'https://www.bizmatch.net/email-authorized';
    }

    // ActionCode-Einstellungen mit der dynamischen URL
    const actionCodeSettings = {
      url: `${verificationUrl}?email=${email}`,
      handleCodeInApp: true,
    };

    // Benutzer erstellen
    const userCredential = await createUserWithEmailAndPassword(this.auth, email, password);

    // E-Mail-Verifizierung mit den angepassten ActionCode-Einstellungen senden
    if (userCredential.user) {
      //await sendEmailVerification(userCredential.user, actionCodeSettings);
      this.mailService.sendVerificationEmail(userCredential.user.email).subscribe({
        next: () => {
          console.log('Verification email sent successfully');
          // Erfolgsmeldung anzeigen
        },
        error: error => {
          console.error('Error sending verification email', error);
          // Fehlermeldung anzeigen
        },
      });
    }

    // const token = await userCredential.user.getIdToken();
    // localStorage.setItem('authToken', token);
    // localStorage.setItem('refreshToken', userCredential.user.refreshToken);
    // if (userCredential.user.photoURL) {
    //   localStorage.setItem('photoURL', userCredential.user.photoURL);
    // }

    return userCredential;
  }

  // Login mit Email und Passwort
  loginWithEmail(email: string, password: string): Promise<UserCredential> {
    return signInWithEmailAndPassword(this.auth, email, password).then(async userCredential => {
      if (userCredential.user) {
        const token = await userCredential.user.getIdToken();
        localStorage.setItem('authToken', token);
        localStorage.setItem('refreshToken', userCredential.user.refreshToken);
        if (userCredential.user.photoURL) {
          localStorage.setItem('photoURL', userCredential.user.photoURL);
        }
        this.loadRoleFromToken();
      }
      return userCredential;
    });
  }

  // Login mit Google
  loginWithGoogle(): Promise<UserCredential> {
    const provider = new GoogleAuthProvider();
    return signInWithPopup(this.auth, provider).then(async userCredential => {
      if (userCredential.user) {
        const token = await userCredential.user.getIdToken();
        localStorage.setItem('authToken', token);
        localStorage.setItem('refreshToken', userCredential.user.refreshToken);
        if (userCredential.user.photoURL) {
          localStorage.setItem('photoURL', userCredential.user.photoURL);
        }
        this.loadRoleFromToken();
      }
      return userCredential;
    });
  }

  // Logout: Token, RefreshToken und photoURL entfernen
  logout(): Promise<void> {
    localStorage.removeItem('authToken');
    localStorage.removeItem('refreshToken');
    localStorage.removeItem('photoURL');
    this.clearRoleCache();
    this.userRoleSubject.next(null);
    return this.auth.signOut();
  }
  isAdmin(): Observable<boolean> {
    return this.getUserRole().pipe(
      map(role => role === 'admin'),
      // take(1) ist optional - es beendet die Subscription, nachdem ein Wert geliefert wurde
      // Nützlich, wenn du die Methode in einem Template mit dem async pipe verwendest
      take(1),
    );
  }
  // Get current user's role from the server with caching
  getUserRole(): Observable<UserRole | null> {
    const now = Date.now();

    // Cache zurücksetzen, wenn die Caching-Zeit abgelaufen ist oder kein Cache existiert
    if (!this.cachedUserRole$ || now - this.lastCacheTime > this.cacheDuration) {
      this.lastCacheTime = now;
      let headers = new HttpHeaders().set('X-Hide-Loading', 'true').set('Accept-Language', 'en-US');
      this.cachedUserRole$ = this.http.get<{ role: UserRole | null }>(`${environment.apiBaseUrl}/bizmatch/auth/me/role`, { headers }).pipe(
        map(response => response.role),
        tap(role => this.userRoleSubject.next(role)),
        catchError(error => {
          console.error('Error fetching user role', error);
          return of(null);
        }),
        // Cache für mehrere Subscriber und behalte den letzten Wert
        // Der Parameter 1 gibt an, dass der letzte Wert gecacht werden soll
        // refCount: false bedeutet, dass der Cache nicht zurückgesetzt wird, wenn keine Subscriber mehr da sind
        shareReplay({ bufferSize: 1, refCount: false }),
      );
    }

    return this.cachedUserRole$;
  }
  clearRoleCache(): void {
    this.cachedUserRole$ = null;
    this.lastCacheTime = 0;
  }
  // Check if user has a specific role
  hasRole(role: UserRole): Observable<boolean> {
    return this.userRole$.pipe(
      map(userRole => {
        if (role === 'guest') {
          // Any authenticated user can access guest features
          return userRole !== null;
        } else if (role === 'pro') {
          // Both pro and admin can access pro features
          return userRole === 'pro' || userRole === 'admin';
        } else if (role === 'admin') {
          // Only admin can access admin features
          return userRole === 'admin';
        }
        return false;
      }),
    );
  }

  // Force refresh the token to get updated custom claims
  async refreshUserClaims(): Promise<void> {
    this.clearRoleCache();
    if (this.auth.currentUser) {
      await this.auth.currentUser.getIdToken(true);
      const token = await this.auth.currentUser.getIdToken();
      localStorage.setItem('authToken', token);
      this.loadRoleFromToken();
    }
  }
  // Prüft, ob ein Token noch gültig ist (über die "exp"-Eigenschaft)
  private isTokenValid(token: string): boolean {
    try {
      const payloadBase64 = token.split('.')[1];
      const payloadJson = atob(payloadBase64.replace(/-/g, '+').replace(/_/g, '/'));
      const payload = JSON.parse(payloadJson);
      const exp = payload.exp;
      const now = Math.floor(Date.now() / 1000);
      return exp > now;
    } catch (e) {
      return false;
    }
  }
  private isEMailVerified(token: string): boolean {
    try {
      const payloadBase64 = token.split('.')[1];
      const payloadJson = atob(payloadBase64.replace(/-/g, '+').replace(/_/g, '/'));
      const payload = JSON.parse(payloadJson);
      return payload.email_verified;
    } catch (e) {
      return false;
    }
  }
  // Versucht, mit dem RefreshToken einen neuen Access Token zu erhalten
  async refreshToken(): Promise<string | null> {
    const storedRefreshToken = localStorage.getItem('refreshToken');
    if (!storedRefreshToken) {
      return null;
    }
    const apiKey = environment.firebaseConfig.apiKey; // Stelle sicher, dass dieser Wert in Deiner environment.ts gesetzt ist
    const url = `https://securetoken.googleapis.com/v1/token?key=${apiKey}`;

    const body = new HttpParams().set('grant_type', 'refresh_token').set('refresh_token', storedRefreshToken);

    const headers = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' });

    try {
      const response: any = await firstValueFrom(this.http.post(url, body.toString(), { headers }));
      // response enthält z. B. id_token, refresh_token, expires_in etc.
      const newToken = response.id_token;
      const newRefreshToken = response.refresh_token;
      localStorage.setItem('authToken', newToken);
      localStorage.setItem('refreshToken', newRefreshToken);
      return newToken;
    } catch (error) {
      console.error('Error refreshing token:', error);
      return null;
    }
  }

  /**
   * Gibt einen gültigen Token zurück.
   * Falls der gespeicherte Token noch gültig ist, wird er zurückgegeben.
   * Ansonsten wird versucht, einen neuen Token mit dem RefreshToken zu holen.
   * Ist auch das nicht möglich, wird null zurückgegeben.
   */
  async getToken(): Promise<string | null> {
    const token = localStorage.getItem('authToken');
    if (token && !this.isEMailVerified(token)) {
      return null;
    } else if (token && this.isTokenValid(token) && this.isEMailVerified(token)) {
      return token;
    } else {
      return await this.refreshToken();
    }
  }

  // Add this new method to sign in with a custom token
  async signInWithCustomToken(token: string): Promise<void> {
    try {
      // Sign in to Firebase with the custom token
      const userCredential = await signInWithCustomToken(this.auth, token);

      // Store the authentication token
      if (userCredential.user) {
        const idToken = await userCredential.user.getIdToken();
        localStorage.setItem('authToken', idToken);
        localStorage.setItem('refreshToken', userCredential.user.refreshToken);

        if (userCredential.user.photoURL) {
          localStorage.setItem('photoURL', userCredential.user.photoURL);
        }

        // Load user role from the token
        this.loadRoleFromToken();
      }

      return;
    } catch (error) {
      console.error('Error signing in with custom token:', error);
      throw error;
    }
  }
}
