import {Injectable, signal} from '@angular/core';
import {LoginService} from '@api';
import {Observable, throwError, timer} from 'rxjs';
import {catchError, tap} from 'rxjs/operators';
import {MatSnackBar} from '@angular/material/snack-bar';
import {JwtHelperService} from '@auth0/angular-jwt';
import {JwtClaims} from '@custom-types/jwt_interface'

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private isAuthenticatedSubject = signal<boolean>(false);
  public readonly isAuthenticated$ = this.isAuthenticatedSubject.asReadonly();

  private userClaimsSubject = signal<JwtClaims | null>(null);
  private jwtHelper = new JwtHelperService();
  private _username = signal<string | null>(null);
  public readonly username = this._username.asReadonly();

  constructor(
    private loginService: LoginService,
    private snackBar: MatSnackBar
  ) {
    // Check if user is already logged in on service initialization
    this.checkAuthStatus();

    timer(1000).subscribe(() => {
      this.reloadUsername();
    });
  }

  /**
   * Attempt to login with the provided code
   */
  public login(code: string): Observable<any> {
    return this.loginService.login(code).pipe(
      tap(jwt => {
        this.saveJwt(jwt);
        this.isAuthenticatedSubject.set(true);

        this.reloadUsername();
      }),
      catchError(error => {
        this.snackBar.open('Login failed', '', {duration: 2000});
        return throwError(() => error);
      })
    );
  }

  private reloadUsername() {
    if (window.location.hostname === 'localhost') {
      console.log("Reloading username for localhost");
      this._username.set('developer');
    }
    if (!this.isAuthenticated$()) {
      return;
    }
    this.loginService.getUsername().subscribe({
      next: (username) => {
        console.log("Username retrieved: " + username.username);
        this._username.set(username.username);
      },
      error: (error) => {
        console.error("Error retrieving username:", error);
        return throwError(() => error);
      }
    });
  }

  /**
   * Log the user out by removing the JWT
   */
  public logout(): void {
    localStorage.removeItem('jwt');
    this.isAuthenticatedSubject.set(false);
    this.userClaimsSubject.set(null);
    this._username.set(null);
  }

  /**
   * Check if the user is authenticated
   */
  private checkAuthStatus(): boolean {
    const jwt = this.getJwt();
    if (!jwt) {
      console.log("No JWT found");
      return false;
    }
    try {
      if (this.jwtHelper.isTokenExpired(jwt)) {
        this.logout();
        console.log("Token expired, logging out");
        return false;
      }

      const claims = this.extractJwtClaims(jwt);
      this.userClaimsSubject.set(claims);
      this.isAuthenticatedSubject.set(true);
      this.reloadUsername();
      return true;
    } catch (e) {
      this.logout();
    }
    return false;
  }

  /**
   * Get the JWT from localStorage
   */
  public getJwt(): string | null {
    return localStorage.getItem('jwt');
  }

  /**
   * Save the JWT to localStorage
   */
  private saveJwt(jwt: string): void {
    localStorage.setItem('jwt', jwt);

    const claims = this.extractJwtClaims(jwt);
    this.userClaimsSubject.set(claims);
  }

  /**
   * Extract claims from JWT
   */
  private extractJwtClaims(jwt: string): JwtClaims {
    return <JwtClaims>this.jwtHelper.decodeToken(jwt);
  }

  /**
   * Get user authorizations from claims
   */
  public getUserAuthorizations(): string[] {
    const claims = this.userClaimsSubject();
    return claims?.authorities || [];
  }

  public hasAccess(requiredAuthorizations: string[]): boolean {
    const userAuthorizations = this.getUserAuthorizations();
    return requiredAuthorizations.some(auth => userAuthorizations.includes(auth));
  }

  public getUuid(): string | null {
    const jwtClaims = this.userClaimsSubject();
    if (jwtClaims === null) {
      return null;
    }
    return jwtClaims.sub ?? null;
  }
}
