import { computed, Injectable, inject, signal } from '@angular/core';
import { BehaviorSubject, Observable, catchError, finalize, map, shareReplay, throwError } from 'rxjs';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { LoginRequest, LoginResponse, UserProfile } from '../../models/auth/auth.model';
import { TokenStorageService } from './token-storage.service';
import { AppContextService } from '../../services/context/app-context.service';
import { AUTH_ENDPOINTS } from '../../../core/end-points/auth/auth.endpoints';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly http = inject(HttpClient);
  private readonly tokenStorage = inject(TokenStorageService);
  private readonly appContextService = inject(AppContextService);

  private readonly userSubject = new BehaviorSubject<UserProfile | null>(null);
  readonly user$ = this.userSubject.asObservable();
  readonly currentUserSignal = signal<UserProfile | null>(null);
  private readonly accessTokenSignal = signal<string | null>(null);
  readonly isAuthenticatedSignal = computed(() => !!this.accessTokenSignal() && !!this.currentUserSignal());

  private refreshRequest$: Observable<LoginResponse> | null = null;

  constructor() {
    this.restoreAuthState();
  }

  login(payload: LoginRequest, rememberMe: boolean): Observable<LoginResponse> {
    return this.http.post<LoginResponse>(`${AUTH_ENDPOINTS.login}`, payload).pipe(
      map((response) => {
        const user = this.normalizeUserProfile(response);
        this.tokenStorage.saveAuth(response, rememberMe);
        this.setAuthState(user, this.tokenStorage.getAccessToken());
        return response;
      })
    );
  }

  refreshAccessToken(): Observable<LoginResponse> {
    if (this.refreshRequest$) {
      return this.refreshRequest$;
    }

    const refreshToken = this.tokenStorage.getRefreshToken();
    if (!refreshToken) {
      this.logout();
      return throwError(() => new HttpErrorResponse({ status: 401, statusText: 'Refresh token missing' }));
    }

    this.refreshRequest$ = this.http.post<LoginResponse>(`${AUTH_ENDPOINTS.refresh}`, { refreshToken }).pipe(
      map((response) => {
        const user = this.normalizeUserProfile(response, this.currentUserSignal());
        const storageType = this.tokenStorage.getStorageType();
        const rememberMe = storageType === 'local';
        this.tokenStorage.saveAuth(response, rememberMe);
        this.setAuthState(user, this.tokenStorage.getAccessToken());
        return response;
      }),
      catchError((error) => {
        this.logout();
        return throwError(() => error);
      }),
      finalize(() => {
        this.refreshRequest$ = null;
      }),
      shareReplay(1)
    );

    return this.refreshRequest$;
  }

  logout(): void {
    this.tokenStorage.clearAuth();
    this.appContextService.clearContext();
    this.setAuthState(null, null);
    this.refreshRequest$ = null;
  }

  get accessToken(): string | null {
    return this.tokenStorage.getAccessToken();
  }

  get refreshToken(): string | null {
    return this.tokenStorage.getRefreshToken();
  }

  get isAuthenticated(): boolean {
    return this.isAuthenticatedSignal();
  }

  get isLoggedIn(): boolean {
    return this.isAuthenticatedSignal();
  }

  get currentUser(): UserProfile | null {
    return this.currentUserSignal();
  }

  private restoreAuthState(): void {
    const storedUser = this.tokenStorage.getUser();
    const storedAccessToken = this.tokenStorage.getAccessToken();

    if (storedUser && storedAccessToken) {
      this.setAuthState(storedUser, storedAccessToken);
      return;
    }

    this.setAuthState(null, storedAccessToken ?? null);
  }

  private setAuthState(user: UserProfile | null, token: string | null): void {
    this.userSubject.next(user);
    this.currentUserSignal.set(user);
    this.accessTokenSignal.set(token);
  }

  private normalizeUserProfile(response: LoginResponse, fallbackUser: UserProfile | null = null): UserProfile | null {
    if (response.user) {
      return response.user;
    }

    const userId = response.userId?.trim();
    const email = response.email?.trim();
    if (userId || email) {
      return {
        id: userId ?? fallbackUser?.id ?? '',
        email: email ?? fallbackUser?.email ?? '',
        roles: response.roles,
      };
    }

    return fallbackUser;
  }
}
