import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { computed, inject, Injectable, PLATFORM_ID, signal, TransferState, makeStateKey } from '@angular/core';
import { firstValueFrom, map, Observable, of, tap } from 'rxjs';

import { environment } from '../../../../environments/environment';
import {
  AuthUser,
  LoginPayload,
  LoginResponse,
  RegisterPayload,
  RegisterResponse,
  UpdateProfilePayload
} from './auth.interfaces';
import { CookieService } from '../cookie/cookie.service';

const AUTH_TOKEN_COOKIE_KEY = 'shopit.auth.token';
const AUTH_USER_SSR_STATE_KEY = makeStateKey<AuthUser>('shopit.auth.user');

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private readonly http = inject(HttpClient);
  private readonly platformId = inject(PLATFORM_ID);
  private readonly cookieService = inject(CookieService);
  private readonly transferState = inject(TransferState);

  private readonly userState = signal<AuthUser | null>(null);
  private readonly tokenState = signal<string | null>(null);

  readonly user = this.userState.asReadonly();
  readonly token = this.tokenState.asReadonly();
  readonly isAuthenticated = computed(() => this.tokenState() !== null);

  login(payload: LoginPayload): Observable<AuthUser> {
    return this.http.post<LoginResponse>(`${environment.url}login`, payload).pipe(
      tap((response) => this.applyAuthenticatedState(response.token, response.user)),
      map((response) => response.user)
    );
  }

  register(payload: RegisterPayload): Observable<RegisterResponse> {
    return this.http.post<RegisterResponse>(`${environment.url}register`, payload);
  }

  updateProfile(payload: UpdateProfilePayload): Observable<AuthUser> {
    return this.http.put<AuthUser>(`${environment.url}me`, payload).pipe(
      tap((user) => this.userState.set(user))
    );
  }

  logout(): Observable<void> {
    if (!this.tokenState()) {
      this.clearSession();
      return of(void 0);
    }

    return this.http.post<void>(`${environment.url}logout`, {}).pipe(
      tap({
        next: () => this.clearSession(),
        error: () => this.clearSession()
      })
    );
  }

  async bootstrap(): Promise<void> {
    this.hydrateSession();

    if (!this.tokenState() || this.userState()) {
      return;
    }

    const transferredUser = this.transferState.get(AUTH_USER_SSR_STATE_KEY, null);
    if (transferredUser) {
      this.transferState.remove(AUTH_USER_SSR_STATE_KEY);
      this.userState.set(transferredUser);
      return;
    }

    try {
      const user = await firstValueFrom(this.loadCurrentUser());
      if (isPlatformServer(this.platformId)) {
        this.transferState.set(AUTH_USER_SSR_STATE_KEY, user);
      }
    } catch (error) {
      if (this.isUnauthorizedError(error)) {
        this.clearSession();
        return;
      }

      throw error;
    }
  }

  loadCurrentUser(): Observable<AuthUser> {
    return this.http.get<AuthUser>(`${environment.url}me`).pipe(
      tap((user) => this.userState.set(user))
    );
  }

  hydrateSession(): void {
    const token = this.cookieService.get(AUTH_TOKEN_COOKIE_KEY);
    this.tokenState.set(token);
  }

  clearSession(): void {
    this.userState.set(null);
    this.tokenState.set(null);
    this.cookieService.delete(AUTH_TOKEN_COOKIE_KEY);
  }

  private applyAuthenticatedState(token: string, user: AuthUser): void {
    this.tokenState.set(token);
    this.userState.set(user);
    this.cookieService.set(AUTH_TOKEN_COOKIE_KEY, token);
  }

  private isUnauthorizedError(error: unknown): error is HttpErrorResponse {
    return error instanceof HttpErrorResponse && error.status === 401;
  }
}
