/**
 * @fileoverview Authentication Service - JWT token management and API communication
 * @version 1.0.0
 * @since 2025-08-04
 * @lastUpdated 2025-08-04
 * @module AuthService
 * @description Singleton service managing user authentication, JWT tokens, and auth-related API calls
 * @contributors Claude Code Agent
 * @dependencies JWT token storage, Backend auth API endpoints
 * @requirements REQ-AUTH-001 (User Authentication System)
 * @testCoverage Authentication flow, token management, session handling
 */

export interface User {
  id: string;
  username: string;
  email?: string;
  role?: string;
  createdAt?: string;
}

export interface AuthState {
  isAuthenticated: boolean;
  user: User | null;
  token: string | null;
  refreshToken: string | null;
  isLoading: boolean;
  error: string | null;
}

export interface LoginCredentials {
  username: string;
  password: string;
}

export interface AuthResponse {
  success: boolean;
  token: string;
  refreshToken: string;
  user: User;
  expiresIn: number;
}

export interface RefreshResponse {
  success: boolean;
  token: string;
  expiresIn: number;
}

export class AuthService {
  private static instance: AuthService;
  private static readonly TOKEN_KEY = 'quiz-auth-token';
  private static readonly REFRESH_TOKEN_KEY = 'quiz-refresh-token';
  private static readonly USER_KEY = 'quiz-user';

  private authState: AuthState;
  private listeners: Set<(state: AuthState) => void> = new Set();
  private refreshTimer: number | null = null;

  private constructor() {
    this.authState = this.getInitialState();
    this.setupTokenRefresh();
  }

  /**
   * Get singleton instance of AuthService
   *
   * @description Returns the singleton instance, creating it if necessary.
   *              Follows the established pattern used by other services.
   *
   * @returns {AuthService} The singleton instance
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-001 (Singleton authentication service)
   */
  static getInstance(): AuthService {
    if (!AuthService.instance) {
      AuthService.instance = new AuthService();
    }
    return AuthService.instance;
  }

  /**
   * Get initial authentication state from localStorage
   *
   * @description Initializes auth state by checking for stored tokens and user data.
   *              Validates token expiration and clears expired tokens.
   *
   * @returns {AuthState} Initial authentication state
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-002 (Persistent authentication state)
   */
  private getInitialState(): AuthState {
    try {
      const token = localStorage.getItem(AuthService.TOKEN_KEY);
      const refreshToken = localStorage.getItem(AuthService.REFRESH_TOKEN_KEY);
      const userStr = localStorage.getItem(AuthService.USER_KEY);

      if (token && userStr) {
        // Check if token is expired
        if (this.isTokenExpired(token)) {
          // Clear expired tokens
          this.clearStoredAuth();
          return this.getUnauthenticatedState();
        }

        const user = JSON.parse(userStr);
        return {
          isAuthenticated: true,
          user,
          token,
          refreshToken,
          isLoading: false,
          error: null,
        };
      }
    } catch (error) {
      console.warn('Failed to load auth state from localStorage:', error);
      this.clearStoredAuth();
    }

    return this.getUnauthenticatedState();
  }

  /**
   * Get unauthenticated state object
   *
   * @description Returns the default state for unauthenticated users.
   *
   * @returns {AuthState} Unauthenticated state
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private getUnauthenticatedState(): AuthState {
    return {
      isAuthenticated: false,
      user: null,
      token: null,
      refreshToken: null,
      isLoading: false,
      error: null,
    };
  }

  /**
   * Get current authentication state
   *
   * @description Returns a copy of the current auth state to prevent mutations.
   *
   * @returns {AuthState} Current authentication state
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-003 (Auth state access)
   */
  getAuthState(): AuthState {
    return { ...this.authState };
  }

  /**
   * Subscribe to authentication state changes
   *
   * @description Allows components to subscribe to auth state changes.
   *              Returns unsubscribe function following the established pattern.
   *
   * @param {Function} callback - Function to call when auth state changes
   * @returns {Function} Unsubscribe function
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-004 (Reactive authentication state)
   */
  subscribe(callback: (state: AuthState) => void): () => void {
    this.listeners.add(callback);
    return () => this.listeners.delete(callback);
  }

  /**
   * Update authentication state and notify listeners
   *
   * @description Updates the internal auth state and notifies all subscribers.
   *              Follows the reactive pattern used by other services.
   *
   * @param {Partial<AuthState>} updates - Partial state updates
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-004 (Reactive state updates)
   */
  private setState(updates: Partial<AuthState>): void {
    this.authState = { ...this.authState, ...updates };
    this.notifyListeners();
  }

  /**
   * Notify all subscribers of state changes
   *
   * @description Calls all registered listeners with the current auth state.
   *              Includes error handling to prevent one listener from breaking others.
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private notifyListeners(): void {
    this.listeners.forEach(callback => {
      try {
        callback(this.getAuthState());
      } catch (error) {
        console.error('Error in auth state listener:', error);
      }
    });
  }

  /**
   * Authenticate user with username and password
   *
   * @description Sends login request to backend API and handles the response.
   *              Stores tokens and user data on successful authentication.
   *
   * @param {LoginCredentials} credentials - Username and password
   * @returns {Promise<boolean>} Success status
   *
   * @throws {Error} When authentication fails or network error occurs
   *
   * @example
   * ```typescript
   * const success = await authService.login({
   *   username: 'john@example.com',
   *   password: 'password123'
   * });
   * ```
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-005 (User login functionality)
   * @accessibility Provides clear error messages for screen readers
   */
  async login(credentials: LoginCredentials): Promise<boolean> {
    this.setState({ isLoading: true, error: null });

    try {
      const response = await fetch('/auth/login', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(credentials),
        credentials: 'same-origin', // Include cookies for CSRF protection
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(errorData.message || `Authentication failed: ${response.status}`);
      }

      const data: AuthResponse = await response.json();

      if (!data.success || !data.token || !data.user) {
        throw new Error('Invalid response from authentication server');
      }

      // Store authentication data
      this.storeAuthData(data);

      // Update state
      this.setState({
        isAuthenticated: true,
        user: data.user,
        token: data.token,
        refreshToken: data.refreshToken,
        isLoading: false,
        error: null,
      });

      // Setup token refresh
      this.setupTokenRefresh(data.expiresIn);

      console.log('✅ User authenticated successfully:', data.user.username);
      return true;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Authentication failed';
      console.error('❌ Authentication failed:', errorMessage);

      this.setState({
        isLoading: false,
        error: errorMessage,
      });

      return false;
    }
  }

  /**
   * Log out current user
   *
   * @description Sends logout request to backend and clears local auth data.
   *              Handles both successful and failed logout attempts gracefully.
   *
   * @returns {Promise<void>}
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-006 (User logout functionality)
   */
  async logout(): Promise<void> {
    try {
      // Attempt to notify backend of logout
      if (this.authState.token) {
        await fetch('/auth/logout', {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${this.authState.token}`,
            'Content-Type': 'application/json',
          },
          credentials: 'same-origin',
        }).catch(error => {
          console.warn('Failed to notify backend of logout:', error);
        });
      }
    } finally {
      // Always clear local auth data regardless of backend response
      this.clearAuth();
      console.log('✅ User logged out successfully');
    }
  }

  /**
   * Refresh authentication token
   *
   * @description Uses refresh token to get new access token.
   *              Automatically logs out user if refresh fails.
   *
   * @returns {Promise<boolean>} Success status
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-007 (Token refresh functionality)
   */
  async refreshToken(): Promise<boolean> {
    if (!this.authState.refreshToken) {
      console.warn('No refresh token available');
      return false;
    }

    try {
      const response = await fetch('/auth/refresh', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          refreshToken: this.authState.refreshToken,
        }),
        credentials: 'same-origin',
      });

      if (!response.ok) {
        throw new Error(`Token refresh failed: ${response.status}`);
      }

      const data: RefreshResponse = await response.json();

      if (!data.success || !data.token) {
        throw new Error('Invalid refresh response');
      }

      // Update token
      localStorage.setItem(AuthService.TOKEN_KEY, data.token);
      this.setState({ token: data.token });

      // Setup next refresh
      this.setupTokenRefresh(data.expiresIn);

      console.log('✅ Token refreshed successfully');
      return true;
    } catch (error) {
      console.error('❌ Token refresh failed:', error);
      this.clearAuth();
      return false;
    }
  }

  /**
   * Get current user information
   *
   * @description Fetches current user data from backend API.
   *              Updates local user data if successful.
   *
   * @returns {Promise<User | null>} User data or null if failed
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-008 (User profile access)
   */
  async getCurrentUser(): Promise<User | null> {
    if (!this.authState.token) {
      return null;
    }

    try {
      const response = await fetch('/auth/me', {
        headers: {
          Authorization: `Bearer ${this.authState.token}`,
        },
        credentials: 'same-origin',
      });

      if (!response.ok) {
        if (response.status === 401) {
          // Token expired, try to refresh
          const refreshed = await this.refreshToken();
          if (!refreshed) {
            return null;
          }
          // Retry with new token
          return this.getCurrentUser();
        }
        throw new Error(`Failed to get user: ${response.status}`);
      }

      const data = await response.json();
      const user = data.user || data;

      // Update stored user data
      localStorage.setItem(AuthService.USER_KEY, JSON.stringify(user));
      this.setState({ user });

      return user;
    } catch (error) {
      console.error('Failed to get current user:', error);
      return null;
    }
  }

  /**
   * Check authentication health
   *
   * @description Verifies that authentication system is working properly.
   *              Can be used for health checks and debugging.
   *
   * @returns {Promise<boolean>} Health status
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-009 (Authentication health check)
   */
  async checkAuthHealth(): Promise<boolean> {
    try {
      const response = await fetch('/auth/health');
      return response.ok;
    } catch (error) {
      console.error('Auth health check failed:', error);
      return false;
    }
  }

  /**
   * Store authentication data in localStorage
   *
   * @description Safely stores auth tokens and user data in localStorage.
   *              Includes error handling for storage failures.
   *
   * @param {AuthResponse} data - Authentication response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private storeAuthData(data: AuthResponse): void {
    try {
      localStorage.setItem(AuthService.TOKEN_KEY, data.token);
      localStorage.setItem(AuthService.USER_KEY, JSON.stringify(data.user));

      if (data.refreshToken) {
        localStorage.setItem(AuthService.REFRESH_TOKEN_KEY, data.refreshToken);
      }
    } catch (error) {
      console.error('Failed to store auth data:', error);
      throw new Error('Failed to save authentication data');
    }
  }

  /**
   * Clear all authentication data
   *
   * @description Removes all auth data from localStorage and resets state.
   *              Cancels any pending token refresh timers.
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private clearAuth(): void {
    this.clearStoredAuth();

    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
      this.refreshTimer = null;
    }

    this.setState(this.getUnauthenticatedState());
  }

  /**
   * Clear stored authentication data from localStorage
   *
   * @description Removes auth tokens and user data from localStorage.
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private clearStoredAuth(): void {
    localStorage.removeItem(AuthService.TOKEN_KEY);
    localStorage.removeItem(AuthService.REFRESH_TOKEN_KEY);
    localStorage.removeItem(AuthService.USER_KEY);
  }

  /**
   * Check if JWT token is expired
   *
   * @description Decodes JWT token and checks expiration time.
   *              Includes buffer time to prevent edge cases.
   *
   * @param {string} token - JWT token to check
   * @returns {boolean} Whether token is expired
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private isTokenExpired(token: string): boolean {
    try {
      const payload = JSON.parse(atob(token.split('.')[1]));
      const now = Math.floor(Date.now() / 1000);
      const bufferTime = 60; // 60 seconds buffer

      return payload.exp < now + bufferTime;
    } catch (error) {
      console.warn('Failed to decode token:', error);
      return true; // Treat invalid tokens as expired
    }
  }

  /**
   * Setup automatic token refresh
   *
   * @description Sets up timer to refresh token before expiration.
   *              Calculates refresh time based on token expiration.
   *
   * @param {number} expiresIn - Token expiration time in seconds
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-007 (Automatic token refresh)
   */
  private setupTokenRefresh(expiresIn?: number): void {
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
    }

    if (!this.authState.refreshToken) {
      return;
    }

    // Default to 15 minutes if no expiration provided
    const expires = expiresIn || 15 * 60;
    // Refresh 2 minutes before expiration, or at 80% of token lifetime
    const refreshTime = Math.min(expires - 120, expires * 0.8) * 1000;

    if (refreshTime > 0) {
      this.refreshTimer = window.setTimeout(() => {
        this.refreshToken().catch(error => {
          console.error('Automatic token refresh failed:', error);
        });
      }, refreshTime);
    }
  }

  /**
   * Get authorization header value
   *
   * @description Returns formatted Authorization header for API requests.
   *              Returns null if no token is available.
   *
   * @returns {string | null} Authorization header value
   *
   * @example
   * ```typescript
   * const authHeader = authService.getAuthHeader();
   * if (authHeader) {
   *   headers.Authorization = authHeader;
   * }
   * ```
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-010 (API request authentication)
   */
  getAuthHeader(): string | null {
    return this.authState.token ? `Bearer ${this.authState.token}` : null;
  }

  /**
   * Make authenticated API request
   *
   * @description Helper method for making API requests with authentication.
   *              Automatically handles token refresh on 401 responses.
   *
   * @param {string} url - API endpoint URL
   * @param {RequestInit} options - Fetch options
   * @returns {Promise<Response>} Fetch response
   *
   * @throws {Error} When request fails or authentication is required
   *
   * @example
   * ```typescript
   * const response = await authService.authenticatedFetch('/api/data', {
   *   method: 'POST',
   *   body: JSON.stringify(data)
   * });
   * ```
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-011 (Authenticated API requests)
   */
  async authenticatedFetch(url: string, options: RequestInit = {}): Promise<Response> {
    const authHeader = this.getAuthHeader();

    if (!authHeader) {
      throw new Error('Authentication required');
    }

    const headers = {
      ...options.headers,
      Authorization: authHeader,
      'Content-Type': 'application/json',
    };

    const response = await fetch(url, {
      ...options,
      headers,
      credentials: 'same-origin',
    });

    // Handle token expiration
    if (response.status === 401 && this.authState.refreshToken) {
      const refreshed = await this.refreshToken();

      if (refreshed) {
        // Retry with new token
        const newAuthHeader = this.getAuthHeader();
        if (newAuthHeader) {
          headers.Authorization = newAuthHeader;
          return fetch(url, { ...options, headers, credentials: 'same-origin' });
        }
      }

      // Refresh failed, user needs to log in again
      this.clearAuth();
      throw new Error('Authentication expired');
    }

    return response;
  }
}

// Export singleton instance for compatibility with existing patterns
const authService = AuthService.getInstance();
export default authService;
