import { inject, Injectable, Signal, signal } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
import { Observable, ReplaySubject, of } from 'rxjs';
import { shareReplay, tap, catchError } from 'rxjs/operators';

import { StateStorageService } from 'app/core/auth/state-storage.service';
import { Account } from 'app/core/auth/account.model';
import { ApplicationConfigService } from '../config/application-config.service';

import { SecurityAction } from 'app/security/security-action.model';
import { ISecurityGroupPermission } from 'app/security/security-group/security-group-permission.model';
import { SecurityPermission } from 'app/security/security-permission.model';
import { IOrganization } from 'app/entities/organization/organization.model';

@Injectable({ providedIn: 'root' })
export class AccountService {
  private userIdentity = signal<Account | null>(null);
  private authenticationState = new ReplaySubject<Account | null>(1);
  private accountCache$?: Observable<Account> | null;

  private translateService = inject(TranslateService);
  private http = inject(HttpClient);
  private stateStorageService = inject(StateStorageService);
  private router = inject(Router);
  private applicationConfigService = inject(ApplicationConfigService);

  save(account: Account): Observable<{}> {
    return this.http.post(this.applicationConfigService.getEndpointFor('api/account'), account);
  }

  authenticate(identity: Account | null): void {
    this.userIdentity.set(identity);
    this.authenticationState.next(this.userIdentity());
    if (!identity) {
      this.accountCache$ = null;
    }
  }

  trackCurrentAccount(): Signal<Account | null> {
    return this.userIdentity.asReadonly();
  }

  hasAnyAuthority(authorities: string[] | string): boolean {
    const userIdentity = this.userIdentity();
    if (!userIdentity) {
      return false;
    }
    if (!Array.isArray(authorities)) {
      authorities = [authorities];
    }
    return userIdentity.authorities.some((authority: string) => authorities.includes(authority));
  }

  identity(force?: boolean): Observable<Account | null> {
    if (!this.accountCache$ || force) {
      this.accountCache$ = this.fetch().pipe(
        tap((account: Account) => {
          this.authenticate(account);

          // After retrieve the account info, the language will be changed to
          // the user's preferred language configured in the account setting
          // unless user have choosed other language in the current session
          if (!this.stateStorageService.getLocale()) {
            this.translateService.use(account.langKey);
          }

          this.navigateToStoredUrl();
        }),
        shareReplay(),
      );
    }
    return this.accountCache$.pipe(catchError(() => of(null)));
  }

  getParentOrganization(): IOrganization | null {
    const userIdentity = this.userIdentity();
    if (!userIdentity) {
      return null;
    }
    
    return userIdentity.parentOrganization;
  }

  isAuthenticated(): boolean {
    return this.userIdentity() !== null;
  }

  getAuthenticationState(): Observable<Account | null> {
    return this.authenticationState.asObservable();
  }

  saml2Endpoint(): Observable<string> {
    return this.http.get(this.applicationConfigService.getEndpointFor('api/account/saml2-endpoint'), { responseType: 'text' as 'text' });
  }

  private fetch(): Observable<Account> {
    return this.http.get<Account>(this.applicationConfigService.getEndpointFor('api/account'));
  }

  private navigateToStoredUrl(): void {
    // previousState can be set in the authExpiredInterceptor and in the userRouteAccessService
    // if login is successful, go to stored previousState and clear previousState
    const previousUrl = this.stateStorageService.getUrl();
    if (previousUrl) {
      this.stateStorageService.clearUrl();
      this.router.navigateByUrl(previousUrl);
    }
  }
  
  /* Security Permission evaluation */
  public findPermission(resourceCode: string): ISecurityGroupPermission | null {
    const userIdentity = this.userIdentity();
    if (!userIdentity) {
      return null;
    }
    
    const permission = userIdentity.securityGroup?.securityGroupPermissions?.find(
      (perm) => perm.securityResource?.code === resourceCode
    );
    
    return permission ?? null;
  }
  
  getPermission(evalResource: string, evalAction: SecurityAction): SecurityPermission {
    const userIdentity = this.userIdentity();
    let securityPermission: SecurityPermission = SecurityPermission.NONE;

    // User must be authenticated
    if (!userIdentity) {
      return securityPermission;
    }
    
    // ROLE_ADMIN has permission to all
    if (this.hasAnyAuthority('ROLE_ADMIN')) {
      return SecurityPermission.ALL;
    }

    // User must have a security group. Deny
    if (!userIdentity.securityGroup) {
      return SecurityPermission.NONE;
    }

    const permission = this.findPermission(evalResource);
    if (permission) {
      switch (evalAction) {
        case SecurityAction.CREATE:
          securityPermission = permission.createPermission!;
          break;
        case SecurityAction.READ:
          securityPermission = permission.readPermission!;
          break;
        case SecurityAction.UPDATE:
          securityPermission = permission.updatePermission!;
          break;
        case SecurityAction.DELETE:
          securityPermission = permission.deletePermission!;
          break;
      }
    }

    return securityPermission;
  }

  evalPermission(evalResource: string, evalAction: SecurityAction): boolean {
    const userIdentity = this.userIdentity();

    // User must be authenticated
    if (!userIdentity) {
      return false;
    }
    
    // ROLE_ADMIN has permission to all
    if (this.hasAnyAuthority('ROLE_ADMIN')) {
      return true;
    }

    // User must have a security group
    if (!userIdentity.securityGroup) {
      return false;
    }
    
    const evalResourceCode = evalResource;
    // Any of this will give user permission. 
    // In case of SecurityPermission.HIERARCHY, it needs extra functional evaluation from <Entity>.component.ts
    const evalPermissions = [SecurityPermission.ALL, SecurityPermission.HIERARCHY]; 
    
    const permission = this.findPermission(evalResourceCode);
    if (permission) {
      let actionPermission: SecurityPermission = SecurityPermission.NONE;
      switch (evalAction) {
        case SecurityAction.CREATE:
          actionPermission = permission.createPermission!;
          break;
        case SecurityAction.READ:
          actionPermission = permission.readPermission!;
          break;
        case SecurityAction.UPDATE:
          actionPermission = permission.updatePermission!;
          break;
        case SecurityAction.DELETE:
          actionPermission = permission.deletePermission!;
          break;
        default:
          console.log('Unknown action');
      }

      return evalPermissions.includes(actionPermission);
    }
    
    return false;
  }
    
}
