import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, throwError, timer, EMPTY } from 'rxjs';
import { switchMap, tap, catchError, filter, take, shareReplay } from 'rxjs/operators';
import { AuthHttpService } from './auth-http.service';
import { TokenService } from './token.service';
import {
  LoginRequest,
  LoginResponse,
  RegisterRequest,
  RegisterResponse,
  User,
  TokenPair,
  LogoutRequest,
  PasswordResetRequest,
  PasswordResetConfirmRequest,
  ChangePasswordRequest,
  EmailVerificationRequest,
  ResendVerificationRequest,
  TwoFactorSetupResponse,
  TwoFactorVerifyRequest,
  TwoFactorStatusResponse,
  ApiResponse,
  ApiError
} from '../models/auth.models';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private currentUserSubject = new BehaviorSubject<User | null>(null);
  private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
  private isLoadingSubject = new BehaviorSubject<boolean>(false);

  public currentUser$ = this.currentUserSubject.asObservable();
  public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
  public isLoading$ = this.isLoadingSubject.asObservable();

  private refreshTimer?: any;

  constructor(
    private authHttpService: AuthHttpService,
    private tokenService: TokenService
  ) {
    this.initialize();
  }

  /**
   * Initialize auth service
   */
  private initialize(): void {
    // Initialize storage listener for cross-tab sync
    this.tokenService.initStorageListener();

    // Subscribe to token changes
    this.tokenService.token$.subscribe(token => {
      const isAuthenticated = !!token && this.tokenService.isTokenValid();
      this.isAuthenticatedSubject.next(isAuthenticated);
      
      if (isAuthenticated) {
        this.loadCurrentUser();
        this.scheduleTokenRefresh();
      } else {
        this.currentUserSubject.next(null);
        this.clearRefreshTimer();
      }
    });

    // Check initial authentication state
    if (this.tokenService.isTokenValid()) {
      this.isAuthenticatedSubject.next(true);
      this.loadCurrentUser();
      this.scheduleTokenRefresh();
    }
  }

  /**
   * Configure auth service with base URL
   */
  configure(baseUrl: string): void {
    this.authHttpService.setBaseUrl(baseUrl);
  }

  // Authentication Methods

  /**
   * Register new user
   */
  register(request: RegisterRequest): Observable<RegisterResponse> {
    this.isLoadingSubject.next(true);
    
    return this.authHttpService.register(request).pipe(
      tap(response => {
        this.tokenService.setTokens(response);
        this.currentUserSubject.next(response.user);
        this.isAuthenticatedSubject.next(true);
        this.scheduleTokenRefresh();
      }),
      catchError(error => {
        this.isLoadingSubject.next(false);
        return throwError(() => error);
      }),
      tap(() => this.isLoadingSubject.next(false))
    );
  }

  /**
   * Login user
   */
  login(request: LoginRequest): Observable<LoginResponse> {
    this.isLoadingSubject.next(true);
    
    return this.authHttpService.login(request).pipe(
      tap(response => {
        this.tokenService.setTokens(response);
        this.currentUserSubject.next(response.user);
        this.isAuthenticatedSubject.next(true);
        this.scheduleTokenRefresh();
      }),
      catchError(error => {
        this.isLoadingSubject.next(false);
        return throwError(() => error);
      }),
      tap(() => this.isLoadingSubject.next(false))
    );
  }

  /**
   * Logout user
   */
  logout(revokeRefreshToken = true): Observable<ApiResponse> {
    this.isLoadingSubject.next(true);
    
    const logoutRequest: LogoutRequest = revokeRefreshToken 
      ? { refresh_token: this.tokenService.getRefreshToken() || undefined }
      : {};

    return this.authHttpService.logout(logoutRequest).pipe(
      tap(() => {
        this.clearAuthState();
      }),
      catchError(error => {
        // Even if logout fails on server, clear local state
        this.clearAuthState();
        this.isLoadingSubject.next(false);
        return throwError(() => error);
      }),
      tap(() => this.isLoadingSubject.next(false))
    );
  }

  /**
   * Silently logout (clear local state only)
   */
  logoutSilently(): void {
    this.clearAuthState();
  }

  /**
   * Refresh access token
   */
  refreshToken(): Observable<TokenPair> {
    const refreshToken = this.tokenService.getRefreshToken();
    
    if (!refreshToken) {
      this.logoutSilently();
      return throwError(() => ({ error: 'No refresh token available' } as ApiError));
    }

    return this.authHttpService.refreshToken({ refresh_token: refreshToken }).pipe(
      tap(tokenPair => {
        this.tokenService.setTokens(tokenPair);
        this.scheduleTokenRefresh();
      }),
      catchError(error => {
        // If refresh fails, logout user
        this.logoutSilently();
        return throwError(() => error);
      })
    );
  }

  /**
   * Get current user information
   */
  getCurrentUser(): Observable<User> {
    if (this.currentUserSubject.value) {
      return this.currentUserSubject.asObservable().pipe(
        filter(user => !!user),
        take(1)
      );
    }

    return this.loadCurrentUser();
  }

  /**
   * Load current user from server
   */
  private loadCurrentUser(): Observable<User> {
    return this.authHttpService.getCurrentUser().pipe(
      tap(user => this.currentUserSubject.next(user)),
      catchError(error => {
        // If getting current user fails, might be invalid token
        if (error.status === 401) {
          this.logoutSilently();
        }
        return throwError(() => error);
      }),
      shareReplay(1)
    );
  }

  // User Management

  /**
   * Update user profile
   */
  updateProfile(updates: Partial<User>): Observable<User> {
    return this.authHttpService.updateUserProfile(updates).pipe(
      tap(user => this.currentUserSubject.next(user))
    );
  }

  /**
   * Change password
   */
  changePassword(request: ChangePasswordRequest): Observable<ApiResponse> {
    return this.authHttpService.changePassword(request);
  }

  /**
   * Request password reset
   */
  forgotPassword(request: PasswordResetRequest): Observable<ApiResponse> {
    return this.authHttpService.forgotPassword(request);
  }

  /**
   * Reset password with token
   */
  resetPassword(request: PasswordResetConfirmRequest): Observable<ApiResponse> {
    return this.authHttpService.resetPassword(request);
  }

  /**
   * Verify email
   */
  verifyEmail(request: EmailVerificationRequest): Observable<ApiResponse> {
    return this.authHttpService.verifyEmail(request);
  }

  /**
   * Resend email verification
   */
  resendEmailVerification(request?: ResendVerificationRequest): Observable<ApiResponse> {
    return this.authHttpService.resendEmailVerification(request);
  }

  // Two-Factor Authentication

  /**
   * Setup 2FA
   */
  setup2FA(): Observable<TwoFactorSetupResponse> {
    return this.authHttpService.setup2FA();
  }

  /**
   * Verify 2FA setup
   */
  verify2FASetup(request: TwoFactorVerifyRequest): Observable<ApiResponse> {
    return this.authHttpService.verify2FASetup(request);
  }

  /**
   * Disable 2FA
   */
  disable2FA(): Observable<ApiResponse> {
    return this.authHttpService.disable2FA();
  }

  /**
   * Get 2FA status
   */
  get2FAStatus(): Observable<TwoFactorStatusResponse> {
    return this.authHttpService.get2FAStatus();
  }

  // Token Management

  /**
   * Check if user is authenticated
   */
  isAuthenticated(): boolean {
    return this.isAuthenticatedSubject.value;
  }

  /**
   * Get access token
   */
  getAccessToken(): string | null {
    return this.tokenService.getAccessToken();
  }

  /**
   * Check if user has specific scope
   */
  hasScope(scope: string): boolean {
    return this.tokenService.hasScope(scope);
  }

  /**
   * Check if user has any of the specified scopes
   */
  hasAnyScope(scopes: string[]): boolean {
    return this.tokenService.hasAnyScope(scopes);
  }

  /**
   * Check if user has all of the specified scopes
   */
  hasAllScopes(scopes: string[]): boolean {
    return this.tokenService.hasAllScopes(scopes);
  }

  /**
   * Get user scopes
   */
  getUserScopes(): string[] {
    return this.tokenService.getUserScopes();
  }

  /**
   * Get user ID from token
   */
  getUserId(): string | null {
    return this.tokenService.getUserId();
  }

  /**
   * Get user email from token
   */
  getUserEmail(): string | null {
    return this.tokenService.getUserEmail();
  }

  /**
   * Get user organization from token
   */
  getUserOrganization(): string | null {
    return this.tokenService.getUserOrganization();
  }

  // Private Methods

  /**
   * Clear authentication state
   */
  private clearAuthState(): void {
    this.tokenService.clearTokens();
    this.currentUserSubject.next(null);
    this.isAuthenticatedSubject.next(false);
    this.clearRefreshTimer();
  }

  /**
   * Schedule automatic token refresh
   */
  private scheduleTokenRefresh(): void {
    this.clearRefreshTimer();

    const timeUntilExpiry = this.tokenService.getTimeUntilExpiry();
    if (timeUntilExpiry <= 0) return;

    // Refresh token 2 minutes before expiry
    const refreshTime = Math.max(1000, timeUntilExpiry - (2 * 60 * 1000));

    this.refreshTimer = timer(refreshTime).pipe(
      switchMap(() => {
        if (this.tokenService.isTokenExpiringSoon()) {
          return this.refreshToken();
        }
        return EMPTY;
      }),
      catchError(error => {
        console.warn('Auto token refresh failed:', error);
        return EMPTY;
      })
    ).subscribe();
  }

  /**
   * Clear refresh timer
   */
  private clearRefreshTimer(): void {
    if (this.refreshTimer) {
      this.refreshTimer.unsubscribe();
      this.refreshTimer = undefined;
    }
  }

  /**
   * Check service health
   */
  healthCheck(): Observable<any> {
    return this.authHttpService.healthCheck();
  }
}