import { coerceArray } from '@angular/cdk/coercion';
import { inject, Injectable } from '@angular/core';
import { Config } from '@core/config';
import { isNullOrUndefined } from '@utils/common';
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { BehaviorSubject } from 'rxjs';
import { logger } from '@isa/core/logging';
import { Router } from '@angular/router';

/**
 * Storage key for the URL to redirect to after login
 */
const REDIRECT_URL_KEY = 'auth_redirect_url';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  #logger = logger(() => ({ service: 'AuthService' }));
  #router = inject(Router);

  #initialized = new BehaviorSubject<boolean>(false);
  get initialized$() {
    return this.#initialized.asObservable();
  }

  #authenticated = new BehaviorSubject<boolean>(false);
  get authenticated$() {
    return this.#authenticated.asObservable();
  }

  private _authConfig: AuthConfig;
  constructor(
    private _config: Config,
    private readonly _oAuthService: OAuthService,
  ) {
    this._oAuthService.events?.subscribe((event) => {
      if (event.type === 'token_received') {
        this.#logger.info('SSO token received', () => ({
          tokenExpiration: new Date(
            this._oAuthService.getAccessTokenExpiration(),
          ).toISOString(),
        }));

        // Handle redirect after successful authentication
        setTimeout(() => {
          const redirectUrl = this._getAndClearRedirectUrl();
          if (redirectUrl) {
            this.#logger.debug('Redirecting after authentication', () => ({
              redirectUrl,
            }));

            this.#router.navigateByUrl(redirectUrl);
          }
        }, 100);
      }
    });
  }

  async init() {
    if (this.#initialized.getValue()) {
      this.#logger.error(
        'AuthService initialization attempted twice',
        new Error('Already initialized'),
      );
      throw new Error('AuthService is already initialized');
    }

    this.#logger.info('Initializing AuthService');

    this._authConfig = this._config.get('@core/auth');
    this.#logger.debug('Auth config loaded', () => ({
      issuer: this._authConfig.issuer,
      clientId: this._authConfig.clientId,
      scope: this._authConfig.scope,
    }));

    this._authConfig.redirectUri = window.location.origin;
    this._authConfig.silentRefreshRedirectUri =
      window.location.origin + '/silent-refresh.html';
    this._authConfig.useSilentRefresh = true;

    this.#logger.debug('Auth URIs configured', () => ({
      redirectUri: this._authConfig.redirectUri,
      silentRefreshRedirectUri: this._authConfig.silentRefreshRedirectUri,
    }));

    this._oAuthService.configure(this._authConfig);
    this._oAuthService.tokenValidationHandler = new JwksValidationHandler();

    this.#logger.debug('Setting up automatic silent refresh');
    this._oAuthService.setupAutomaticSilentRefresh();

    this.#logger.debug('Loading discovery document and attempting login');
    const authenticated =
      await this._oAuthService.loadDiscoveryDocumentAndTryLogin();

    this.#authenticated.next(authenticated);
    this.#logger.info('AuthService initialized', () => ({ authenticated }));

    this.#initialized.next(true);
    return authenticated;
  }

  isAuthenticated() {
    return this.#authenticated.getValue();
  }

  isIdTokenValid() {
    const expiration = new Date(this._oAuthService.getIdTokenExpiration());
    const isValid = this._oAuthService.hasValidIdToken();
    this.#logger.debug('ID token validation check', () => ({
      expiration: expiration.toISOString(),
      isValid,
    }));
    return isValid;
  }

  isAccessTokenValid() {
    const expiration = new Date(this._oAuthService.getAccessTokenExpiration());
    const isValid = this._oAuthService.hasValidAccessToken();
    this.#logger.debug('Access token validation check', () => ({
      expiration: expiration.toISOString(),
      isValid,
    }));
    return isValid;
  }

  getToken() {
    return this._oAuthService.getAccessToken();
  }

  getClaims() {
    const token = this._oAuthService.getAccessToken();
    return this.parseJwt(token);
  }

  getClaimByKey(key: string) {
    const claims = this.getClaims();
    if (isNullOrUndefined(claims)) {
      return null;
    }
    return claims[key];
  }

  parseJwt(token: string) {
    if (isNullOrUndefined(token)) {
      return null;
    }

    const base64Url = token.split('.')[1];
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');

    const encoded = window.atob(base64);
    return JSON.parse(encoded);
  }

  /**
   * Saves the URL to redirect to after successful login
   */
  _saveRedirectUrl(): void {
    localStorage.setItem(REDIRECT_URL_KEY, window.location.href);
  }

  /**
   * Gets and clears the saved redirect URL
   */
  _getAndClearRedirectUrl(): string | null {
    const url = localStorage.getItem(REDIRECT_URL_KEY);
    localStorage.removeItem(REDIRECT_URL_KEY);
    return url;
  }

  login() {
    this.#logger.info('Initiating login flow');
    this._saveRedirectUrl();
    this._oAuthService.initLoginFlow();
  }

  setKeyCardToken(token: string) {
    this.#logger.debug('Setting keycard token');
    this._oAuthService.customQueryParams = {
      temp_token: token,
    };
  }

  async logout() {
    this.#logger.info('Initiating logout');
    await this._oAuthService.revokeTokenAndLogout();
    this.#logger.info('Logout completed');
  }

  hasRole(role: string | string[]) {
    const roles = coerceArray(role);

    const userRoles = this.getClaimByKey('role');

    if (isNullOrUndefined(userRoles)) {
      return false;
    }

    return roles.every((r) => userRoles.includes(r));
  }

  async refresh() {
    try {
      this.#logger.debug('Refreshing authentication token');

      if (
        this._authConfig.responseType.includes('code') &&
        this._authConfig.scope.includes('offline_access')
      ) {
        await this._oAuthService.refreshToken();
        this.#logger.info('Token refreshed using refresh token');
      } else {
        await this._oAuthService.silentRefresh();
        this.#logger.info('Token refreshed using silent refresh');
      }
    } catch (error) {
      this.#logger.error('Token refresh failed', error as Error);
    }
  }
}
