import { Injectable, inject, signal, computed, NgZone, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { Observable, tap, BehaviorSubject, Subject, firstValueFrom, catchError, throwError, timeout } from 'rxjs';
import { ApiService } from './api.service';
import { StorageService } from './storage.service';
import { TokenValidator } from '../utils/token-validator';
import { InputSanitizer } from '../utils/input-sanitizer';
import {
  LoginDto,
  LoginResponseDto,
  DigiLockerLoginDto,
  DigiLockerLoginResponseDto,
  CurrentUserDto,
  DepartmentResponseDto,
} from '../../api/models';

export type UserType = 'APPLICANT' | 'DEPARTMENT' | 'ADMIN';

// Timeout for login requests
const LOGIN_TIMEOUT_MS = 30000;

/**
 * Auth Service with Security Enhancements
 *
 * Security features:
 * - Synchronized state management to prevent race conditions
 * - Token validation on state restoration
 * - Input sanitization for all auth operations
 * - Secure logout with state cleanup
 */
@Injectable({
  providedIn: 'root',
})
export class AuthService implements OnDestroy {
  private readonly api = inject(ApiService);
  private readonly storage = inject(StorageService);
  private readonly router = inject(Router);
  private readonly ngZone = inject(NgZone);

  // Use a mutex-like pattern to prevent race conditions
  private stateUpdateInProgress = false;
  private readonly stateUpdateQueue: (() => void)[] = [];

  private readonly currentUserSubject = new BehaviorSubject<CurrentUserDto | null>(null);
  readonly currentUser$ = this.currentUserSubject.asObservable();

  private readonly _currentUser = signal<CurrentUserDto | null>(null);
  readonly currentUser = this._currentUser.asReadonly();

  private readonly _isAuthenticated = signal(false);
  readonly isAuthenticated = this._isAuthenticated.asReadonly();

  private readonly _userType = signal<UserType | null>(null);
  readonly userType = this._userType.asReadonly();

  readonly isDepartment = computed(() => this._userType() === 'DEPARTMENT');
  readonly isApplicant = computed(() => this._userType() === 'APPLICANT');
  readonly isAdmin = computed(() => this._userType() === 'ADMIN');

  // Event emitter for auth state changes
  private readonly authStateChanged = new Subject<{ authenticated: boolean; userType: UserType | null }>();
  readonly authStateChanged$ = this.authStateChanged.asObservable();

  // Loading state
  private readonly _isLoading = signal(false);
  readonly isLoading = this._isLoading.asReadonly();

  // Track storage listener for cleanup
  private storageListener: ((event: StorageEvent) => void) | null = null;

  // Guard to prevent storage listener from interfering during login
  private isLoginInProgress = false;

  constructor() {
    this.loadStoredUser();
    this.setupStorageListener();
  }

  ngOnDestroy(): void {
    // Complete subjects to prevent memory leaks
    this.currentUserSubject.complete();
    this.authStateChanged.complete();

    // Remove storage listener
    if (this.storageListener && typeof window !== 'undefined') {
      window.removeEventListener('storage', this.storageListener);
    }
  }

  /**
   * Load and validate stored user on service initialization
   */
  private loadStoredUser(): void {
    const token = this.storage.getToken();
    const user = this.storage.getUser<CurrentUserDto>();
    console.log('[AUTH DEBUG] loadStoredUser - token:', token ? 'EXISTS' : 'NULL', 'user:', user ? 'EXISTS' : 'NULL');

    // Only restore state if we have both valid token AND user
    if (token && user) {
      // Validate token
      const validation = TokenValidator.validate(token);

      if (validation.valid) {
        // Validate user type
        if (this.isValidUserType(user.type)) {
          this.updateAuthState(user, true);
        } else {
          console.warn('Invalid user type in stored data, clearing...');
          this.clearAuthState();
        }
      } else {
        console.warn('Invalid token on init:', validation.error);
        this.clearAuthState();
      }
    } else if (token || user) {
      // Inconsistent state - clear everything
      console.warn('Inconsistent auth state detected, clearing... token:', !!token, 'user:', !!user);
      // Don't clear if we have a valid token - just log and continue
      // This prevents race conditions from clearing valid auth state
      if (token) {
        console.warn('Token exists but user missing - NOT clearing (likely race condition)');
        return;
      }
      this.clearAuthState();
    }
  }

  /**
   * Listen for storage changes from other tabs/windows
   * Prevents session fixation across tabs
   */
  private setupStorageListener(): void {
    if (typeof window !== 'undefined') {
      this.storageListener = (event: StorageEvent) => {
        console.log('[AUTH DEBUG] Storage event received, key:', event.key, 'isLoginInProgress:', this.isLoginInProgress);
        // Skip if login is in progress to avoid race conditions
        if (this.isLoginInProgress) {
          console.log('[AUTH DEBUG] Skipping storage event during login');
          return;
        }

        this.ngZone.run(() => {
          if (event.key === null || event.key?.includes('token') || event.key?.includes('user')) {
            // Storage was cleared or auth-related key changed
            const token = this.storage.getToken();
            if (!token) {
              this.clearAuthState();
            } else {
              this.loadStoredUser();
            }
          }
        });
      };
      window.addEventListener('storage', this.storageListener);
    }
  }

  /**
   * Validate user type is one of the allowed values
   */
  private isValidUserType(type: unknown): type is UserType {
    return type === 'APPLICANT' || type === 'DEPARTMENT' || type === 'ADMIN';
  }

  /**
   * Thread-safe auth state update
   */
  private updateAuthState(user: CurrentUserDto | null, authenticated: boolean): void {
    // Queue updates if one is in progress
    if (this.stateUpdateInProgress) {
      this.stateUpdateQueue.push(() => this.updateAuthState(user, authenticated));
      return;
    }

    this.stateUpdateInProgress = true;

    try {
      const userType = user?.type ?? null;

      // Update all state atomically
      this._currentUser.set(user);
      this._isAuthenticated.set(authenticated);
      this._userType.set(userType);
      this.currentUserSubject.next(user);

      // Emit state change event
      this.authStateChanged.next({ authenticated, userType });
    } finally {
      this.stateUpdateInProgress = false;

      // Process queued updates
      const nextUpdate = this.stateUpdateQueue.shift();
      if (nextUpdate) {
        nextUpdate();
      }
    }
  }

  /**
   * Clear all auth state
   */
  private clearAuthState(): void {
    this.storage.clear();
    this.updateAuthState(null, false);
  }

  /**
   * Login with email and password
   * Input sanitization is performed before sending
   */
  async login(email: string, password: string): Promise<void> {
    // Sanitize email input
    const sanitizedEmail = InputSanitizer.sanitizeEmail(email);

    if (!sanitizedEmail) {
      throw new Error('Invalid email format');
    }

    // Check for malicious input
    if (InputSanitizer.isMalicious(email) || InputSanitizer.isMalicious(password)) {
      throw new Error('Invalid input detected');
    }

    // Validate password length (don't sanitize password content)
    if (!password || password.length < 8 || password.length > 128) {
      throw new Error('Invalid password format');
    }

    this._isLoading.set(true);
    this.isLoginInProgress = true;

    try {
      const response = await firstValueFrom(
        this.api.postRaw<any>('/auth/login', {
          email: sanitizedEmail,
          password: password,
        }).pipe(
          timeout(LOGIN_TIMEOUT_MS),
          catchError((error) => {
            if (error.name === 'TimeoutError') {
              return throwError(() => new Error('Login request timed out. Please try again.'));
            }
            return throwError(() => error);
          })
        )
      );

      if (!response) {
        throw new Error('Login failed');
      }

    // Validate response token
    console.log('[AUTH DEBUG] Response received:', { hasToken: !!response.accessToken, hasUser: !!response.user });
    if (!response.accessToken || !TokenValidator.isValidFormat(response.accessToken)) {
      console.error('[AUTH DEBUG] Token validation failed:', response.accessToken?.substring(0, 50));
      throw new Error('Invalid authentication response');
    }

    // Validate response user data
    if (!response.user || !response.user.id) {
      console.error('[AUTH DEBUG] User validation failed:', response.user);
      throw new Error('Invalid user data in response');
    }

    console.log('[AUTH DEBUG] About to store token...');
    this.storage.setToken(response.accessToken);
    console.log('[AUTH DEBUG] Token stored, verifying...');
    const storedToken = this.storage.getToken();
    console.log('[AUTH DEBUG] Token retrieved:', storedToken ? 'EXISTS' : 'NULL');

    const userType: UserType =
      response.user.role === 'ADMIN' ? 'ADMIN' :
      response.user.role === 'DEPARTMENT' ? 'DEPARTMENT' : 'APPLICANT';

    const user: CurrentUserDto = {
      id: String(response.user.id), // Ensure string type
      type: userType,
      name: InputSanitizer.sanitizeName(response.user.name || ''),
      email: InputSanitizer.sanitizeEmail(response.user.email || '') || '',
      departmentId: response.user.departmentId,
      walletAddress: response.user.walletAddress,
    };

      this.storage.setUser(user);
      this.updateAuthState(user, true);
    } finally {
      // Delay clearing the login flag to allow async storage events to be ignored
      setTimeout(() => {
        this.isLoginInProgress = false;
        console.log('[AUTH DEBUG] Login guard cleared after delay');
      }, 2000);
      this._isLoading.set(false);
    }
  }

  /**
   * Department login with API key
   * Input sanitization is performed in the component
   */
  departmentLogin(dto: LoginDto): Observable<LoginResponseDto> {
    // Validate DTO
    if (!dto.departmentCode || !dto.apiKey) {
      return throwError(() => new Error('Department code and API key are required'));
    }

    this._isLoading.set(true);

    return this.api.postRaw<LoginResponseDto>('/auth/department/login', dto).pipe(
      timeout(LOGIN_TIMEOUT_MS),
      tap((response) => {
        // Validate response
        if (!response.accessToken || !TokenValidator.isValidFormat(response.accessToken)) {
          throw new Error('Invalid authentication response');
        }

        if (!response.department || !response.department.id) {
          throw new Error('Invalid department data in response');
        }

        this.storage.setToken(response.accessToken);

        const user: CurrentUserDto = {
          id: String(response.department.id),
          type: 'DEPARTMENT',
          name: InputSanitizer.sanitizeName(response.department.name || ''),
          email: InputSanitizer.sanitizeEmail(response.department.contactEmail || '') || '',
          departmentCode: InputSanitizer.sanitizeAlphanumeric(response.department.code || '', '_'),
          departmentId: String(response.department.id),
        };

        this.storage.setUser(user);
        this.updateAuthState(user, true);
        this._isLoading.set(false);
      }),
      catchError((error) => {
        this._isLoading.set(false);
        if (error.name === 'TimeoutError') {
          return throwError(() => new Error('Login request timed out. Please try again.'));
        }
        const message = error instanceof Error ? error.message : 'Department login failed';
        return throwError(() => new Error(message));
      })
    );
  }

  /**
   * DigiLocker login for applicants
   * Input sanitization is performed in the component
   */
  digiLockerLogin(dto: DigiLockerLoginDto): Observable<DigiLockerLoginResponseDto> {
    // Validate DTO
    if (!dto.digilockerId) {
      return throwError(() => new Error('DigiLocker ID is required'));
    }

    // Check for malicious input
    if (InputSanitizer.isMalicious(dto.digilockerId)) {
      return throwError(() => new Error('Invalid DigiLocker ID'));
    }

    this._isLoading.set(true);

    return this.api.postRaw<DigiLockerLoginResponseDto>('/auth/digilocker/login', dto).pipe(
      timeout(LOGIN_TIMEOUT_MS),
      tap((response) => {
        // Validate response
        if (!response.accessToken || !TokenValidator.isValidFormat(response.accessToken)) {
          throw new Error('Invalid authentication response');
        }

        if (!response.applicant || !response.applicant.id) {
          throw new Error('Invalid applicant data in response');
        }

        this.storage.setToken(response.accessToken);

        const user: CurrentUserDto = {
          id: String(response.applicant.id),
          type: 'APPLICANT',
          name: InputSanitizer.sanitizeName(response.applicant.name || ''),
          email: InputSanitizer.sanitizeEmail(response.applicant.email || '') || '',
          digilockerId: InputSanitizer.sanitizeAlphanumeric(response.applicant.digilockerId || '', '-'),
        };

        this.storage.setUser(user);
        this.updateAuthState(user, true);
        this._isLoading.set(false);
      }),
      catchError((error) => {
        this._isLoading.set(false);
        if (error.name === 'TimeoutError') {
          return throwError(() => new Error('Login request timed out. Please try again.'));
        }
        const message = error instanceof Error ? error.message : 'DigiLocker login failed';
        return throwError(() => new Error(message));
      })
    );
  }

  /**
   * Secure logout - clears all auth state
   */
  logout(): void {
    this.clearAuthState();
    this.router.navigate(['/login']);
  }

  /**
   * Force logout with reason (for security events)
   */
  forceLogout(reason: 'session_expired' | 'invalid_token' | 'security_violation' = 'session_expired'): void {
    this.clearAuthState();
    this.router.navigate(['/login'], { queryParams: { reason } });
  }

  getCurrentUser(): CurrentUserDto | null {
    return this.currentUserSubject.value;
  }

  getToken(): string | null {
    return this.storage.getToken();
  }

  hasRole(role: UserType): boolean {
    return this._userType() === role;
  }

  hasAnyRole(roles: UserType[]): boolean {
    const currentType = this._userType();
    return currentType !== null && roles.includes(currentType);
  }
}
