// File: src/services/Manager/AuthManager.js

import RegisterService from "../API/RegisterService.js";
import LoginService from "../API/LoginService.js";
import RefreshTokenService from "../API/RefreshTokenService.js";

/**
 * AuthManager
 *
 * Manages authentication state and operations for MaplePress.
 * Handles token storage, user session, and authentication lifecycle.
 */
class AuthManager {
  constructor() {
    this.accessToken = null;
    this.refreshToken = null;
    this.accessExpiry = null;
    this.refreshExpiry = null;
    this.user = null;
    this.tenant = null;
    this.sessionId = null;
    this.isInitialized = false;
    this.refreshPromise = null; // Track ongoing refresh to prevent duplicates

    // LocalStorage keys
    this.STORAGE_KEYS = {
      ACCESS_TOKEN: "maplepress_access_token",
      REFRESH_TOKEN: "maplepress_refresh_token",
      ACCESS_EXPIRY: "maplepress_access_expiry",
      REFRESH_EXPIRY: "maplepress_refresh_expiry",
      USER: "maplepress_user",
      TENANT: "maplepress_tenant",
      SESSION_ID: "maplepress_session_id",
    };
  }

  /**
   * Initialize the auth manager
   * Loads stored auth state from localStorage
   */
  async initialize() {
    console.log("[AuthManager] 🚀 Initializing...");

    try {
      // Load stored auth state from localStorage
      this.accessToken = localStorage.getItem(this.STORAGE_KEYS.ACCESS_TOKEN);
      this.refreshToken = localStorage.getItem(
        this.STORAGE_KEYS.REFRESH_TOKEN
      );
      this.sessionId = localStorage.getItem(this.STORAGE_KEYS.SESSION_ID);

      console.log("[AuthManager] 📦 Loaded from localStorage:", {
        hasAccessToken: !!this.accessToken,
        hasRefreshToken: !!this.refreshToken,
        hasSessionId: !!this.sessionId,
      });

      // Load expiry dates
      const accessExpiryStr = localStorage.getItem(
        this.STORAGE_KEYS.ACCESS_EXPIRY
      );
      const refreshExpiryStr = localStorage.getItem(
        this.STORAGE_KEYS.REFRESH_EXPIRY
      );

      if (accessExpiryStr) {
        this.accessExpiry = new Date(accessExpiryStr);
      }
      if (refreshExpiryStr) {
        this.refreshExpiry = new Date(refreshExpiryStr);
      }

      console.log("[AuthManager] ⏰ Token expiry times:", {
        accessExpiry: this.accessExpiry?.toISOString(),
        refreshExpiry: this.refreshExpiry?.toISOString(),
        currentTime: new Date().toISOString(),
        accessExpired: this.isTokenExpired(this.accessExpiry),
        refreshExpired: this.isTokenExpired(this.refreshExpiry),
      });

      // Load user and tenant data
      const userStr = localStorage.getItem(this.STORAGE_KEYS.USER);
      const tenantStr = localStorage.getItem(this.STORAGE_KEYS.TENANT);

      if (userStr) {
        this.user = JSON.parse(userStr);
      }
      if (tenantStr) {
        this.tenant = JSON.parse(tenantStr);
      }

      console.log("[AuthManager] 👤 User and tenant loaded:", {
        hasUser: !!this.user,
        hasTenant: !!this.tenant,
        userId: this.user?.id,
        userEmail: this.user?.email,
      });

      // Check if access token is expired but refresh token is still valid
      if (this.accessToken && this.isTokenExpired(this.accessExpiry)) {
        console.log("[AuthManager] ⚠️ Access token expired");

        // Try to refresh if refresh token is still valid
        if (this.refreshToken && !this.isTokenExpired(this.refreshExpiry)) {
          console.log("[AuthManager] 🔄 Attempting to refresh token on initialization");
          try {
            await this.refreshAccessToken();
          } catch (error) {
            console.log("[AuthManager] ❌ Token refresh failed, clearing session");
            this.clearSession();
          }
        } else {
          console.log("[AuthManager] ❌ Refresh token also expired, clearing session");
          this.clearSession();
        }
      }

      this.isInitialized = true;
      const isAuth = this.isAuthenticated();
      console.log("[AuthManager] ✅ Initialized", {
        authenticated: isAuth,
        hasAccessToken: !!this.accessToken,
        hasUser: !!this.user,
        accessExpired: this.isTokenExpired(this.accessExpiry),
      });
    } catch (error) {
      console.error("[AuthManager] ❌ Initialization error:", error);
      this.clearSession();
      this.isInitialized = true;
    }
  }

  /**
   * Check if AuthManager is initialized
   * Returns true if initialization has completed (even if not authenticated)
   */
  getIsInitialized() {
    return this.isInitialized;
  }

  /**
   * Check if user is authenticated
   * Returns false if not yet initialized to prevent premature redirects
   *
   * IMPORTANT: This checks if the REFRESH token is valid, not the access token.
   * Access tokens expire after 15 minutes, but that's OK - they get auto-refreshed.
   * We only care if the refresh token (7 days) is still valid.
   */
  isAuthenticated() {
    // Don't return authentication status until initialized
    // This prevents race conditions where Dashboard checks before localStorage is loaded
    if (!this.isInitialized) {
      console.log("[AuthManager] ⏳ Not yet initialized, returning false");
      return false;
    }

    // User is authenticated if:
    // 1. We have tokens (both access and refresh)
    // 2. We have user data
    // 3. The REFRESH token is still valid (not the access token)
    const hasTokens = this.accessToken !== null && this.refreshToken !== null;
    const hasUser = this.user !== null;
    const refreshTokenValid = !this.isTokenExpired(this.refreshExpiry);

    const isAuth = hasTokens && hasUser && refreshTokenValid;

    if (!isAuth && this.isInitialized) {
      console.log("[AuthManager] 🔐 Authentication check failed:", {
        hasTokens,
        hasUser,
        refreshTokenValid,
        refreshExpiry: this.refreshExpiry?.toISOString(),
      });
    }

    return isAuth;
  }

  /**
   * Check if a token is expired
   * Returns true if the current time is AFTER the expiry time
   */
  isTokenExpired(expiry) {
    if (!expiry) return true;
    return new Date() > expiry;
  }

  /**
   * Get the current access token
   */
  getAccessToken() {
    return this.accessToken;
  }

  /**
   * Get the current user
   */
  getUser() {
    return this.user;
  }

  /**
   * Get the current tenant
   */
  getTenant() {
    return this.tenant;
  }

  /**
   * Store authentication data
   */
  storeAuthData(authResponse) {
    // Store tokens
    this.accessToken = authResponse.accessToken;
    this.refreshToken = authResponse.refreshToken;
    this.accessExpiry = authResponse.accessExpiry;
    this.refreshExpiry = authResponse.refreshExpiry;
    this.sessionId = authResponse.sessionId;

    // Store user data
    this.user = {
      id: authResponse.userId,
      email: authResponse.userEmail,
      name: authResponse.userName,
      role: authResponse.userRole,
    };

    // Store tenant data (handle optional fields for login endpoint)
    this.tenant = {
      id: authResponse.tenantId,
      name: authResponse.tenantName || null,
      slug: authResponse.tenantSlug || null,
    };

    // Persist to localStorage
    localStorage.setItem(this.STORAGE_KEYS.ACCESS_TOKEN, this.accessToken);
    localStorage.setItem(this.STORAGE_KEYS.REFRESH_TOKEN, this.refreshToken);
    localStorage.setItem(
      this.STORAGE_KEYS.ACCESS_EXPIRY,
      this.accessExpiry.toISOString()
    );
    localStorage.setItem(
      this.STORAGE_KEYS.REFRESH_EXPIRY,
      this.refreshExpiry.toISOString()
    );
    localStorage.setItem(this.STORAGE_KEYS.SESSION_ID, this.sessionId);
    localStorage.setItem(this.STORAGE_KEYS.USER, JSON.stringify(this.user));
    localStorage.setItem(
      this.STORAGE_KEYS.TENANT,
      JSON.stringify(this.tenant)
    );

    console.log("[AuthManager] Auth data stored successfully");
  }

  /**
   * Clear session data
   */
  clearSession() {
    this.accessToken = null;
    this.refreshToken = null;
    this.accessExpiry = null;
    this.refreshExpiry = null;
    this.user = null;
    this.tenant = null;
    this.sessionId = null;

    // Clear localStorage
    Object.values(this.STORAGE_KEYS).forEach((key) => {
      localStorage.removeItem(key);
    });

    console.log("[AuthManager] Session cleared");
  }

  /**
   * Login with email and password
   * @param {string} email - User's email address
   * @param {string} password - User's password
   * @returns {Promise<Object>} Authentication response
   */
  async login(email, password) {
    console.log("[AuthManager] Login attempt for:", email);

    try {
      // Call LoginService
      const response = await LoginService.login({ email, password });

      // Store authentication data
      // Note: Login response doesn't include tenant name/slug, so we only store what we have
      const authData = {
        ...response,
        tenantName: null, // Not provided by login endpoint
        tenantSlug: null, // Not provided by login endpoint
      };
      this.storeAuthData(authData);

      console.log("[AuthManager] Login successful");
      return response;
    } catch (error) {
      console.error("[AuthManager] Login failed:", error);
      throw error;
    }
  }

  /**
   * Register a new user
   * @param {Object} registrationData - Registration data
   * @returns {Promise<Object>} Authentication response
   */
  async register(registrationData) {
    console.log("[AuthManager] Registration attempt for:", registrationData.email);

    try {
      // Call RegisterService
      const response = await RegisterService.register(registrationData);

      // Store authentication data
      this.storeAuthData(response);

      console.log("[AuthManager] Registration successful");
      return response;
    } catch (error) {
      console.error("[AuthManager] Registration failed:", error);
      throw error;
    }
  }

  /**
   * Logout the current user
   */
  async logout() {
    console.log("[AuthManager] Logging out");

    // TODO: Call backend to invalidate token
    // TODO: Implement actual logout API call

    // Clear local session
    this.clearSession();

    console.log("[AuthManager] Logout successful");
  }

  /**
   * Refresh the access token using the stored refresh token
   * Implements token rotation - both access and refresh tokens are regenerated
   *
   * @returns {Promise<Object>} New authentication data
   * @throws {Error} If refresh fails (expired, invalid, or session invalidated)
   */
  async refreshAccessToken() {
    // If a refresh is already in progress, return that promise
    if (this.refreshPromise) {
      console.log("[AuthManager] Refresh already in progress, waiting...");
      return this.refreshPromise;
    }

    console.log("[AuthManager] 🔄 Refreshing access token");
    console.log("[AuthManager] 🔍 Current state:", {
      hasRefreshToken: !!this.refreshToken,
      refreshTokenLength: this.refreshToken?.length,
      hasAccessToken: !!this.accessToken,
      isInitialized: this.isInitialized,
    });

    // Check if we have a refresh token
    if (!this.refreshToken) {
      console.error("[AuthManager] ❌ No refresh token available!");
      console.error("[AuthManager] localStorage check:", {
        storedRefreshToken: localStorage.getItem(this.STORAGE_KEYS.REFRESH_TOKEN),
      });
      const error = new Error("No refresh token available");
      this.clearSession();
      throw error;
    }

    // Check if refresh token is expired
    if (this.isTokenExpired(this.refreshExpiry)) {
      const error = new Error("Refresh token expired");
      console.error("[AuthManager] Refresh failed:", error);
      this.clearSession();
      throw error;
    }

    // Create refresh promise and store it to prevent duplicate refreshes
    this.refreshPromise = (async () => {
      try {
        // Call refresh token service
        const response = await RefreshTokenService.refreshToken(this.refreshToken);

        // Store the new authentication data
        // Note: Refresh response has same format as login (no tenant name/slug)
        const authData = {
          ...response,
          tenantName: this.tenant?.name || null,
          tenantSlug: this.tenant?.slug || null,
        };
        this.storeAuthData(authData);

        console.log("[AuthManager] Token refresh successful");
        return response;
      } catch (error) {
        console.error("[AuthManager] Token refresh failed:", error);

        // Clear session on refresh failure
        this.clearSession();
        throw error;
      } finally {
        // Clear the refresh promise
        this.refreshPromise = null;
      }
    })();

    return this.refreshPromise;
  }

  /**
   * Check if the access token needs to be refreshed soon
   * Returns true if token expires within the buffer time (default: 1 minute)
   *
   * @param {number} bufferMinutes - Minutes before expiry to trigger refresh
   * @returns {boolean} True if token should be refreshed
   */
  shouldRefreshToken(bufferMinutes = 1) {
    return RefreshTokenService.shouldRefreshToken(this.accessExpiry, bufferMinutes);
  }

  /**
   * Automatically refresh the access token if needed
   * This should be called before making API requests to ensure the token is valid
   *
   * @returns {Promise<void>}
   */
  async ensureValidToken() {
    // If not authenticated, nothing to refresh
    if (!this.accessToken) {
      return;
    }

    // If token needs refresh, do it
    if (this.shouldRefreshToken()) {
      console.log("[AuthManager] Token expiring soon, refreshing proactively");
      try {
        await this.refreshAccessToken();
      } catch (error) {
        console.error("[AuthManager] Proactive token refresh failed:", error);
        throw error;
      }
    }
  }
}

export default AuthManager;
