/**
 * Token Service - Token refresh and management
 */

import { cacheService } from './cacheService';
import { pocketbaseService } from './pocketbaseService';
import { graphService } from './graphService';
import { CACHE_KEYS, TOKEN_TTL_SECONDS, REFRESH_TOKEN_TTL_SECONDS, TOKEN_REFRESH_INTERVAL_MS } from '@shared/constants';
import type { CachedToken, TokenType, TokenRefreshResponse } from '@shared/types/auth';
import { env } from '../config/env';

class TokenService {
  private refreshLoops = new Map<string, Timer>();
  private activeUsers = new Set<string>();

  /**
   * Check if token is expiring soon (within buffer)
   */
  isExpiring(expiresAt: number): boolean {
    return expiresAt - Date.now() < env.TOKEN_EXPIRY_BUFFER_MS;
  }

  /**
   * Store tokens in cache after login
   */
  async storeTokens(
    userId: string,
    pbToken: string,
    graphToken: string,
    refreshPb: string,
    refreshGraph: string
  ): Promise<void> {
    const expiresAt = Date.now() + TOKEN_TTL_SECONDS * 1000;

    // Store PocketBase token
    await cacheService.set<CachedToken>(
      CACHE_KEYS.TOKEN_PB(userId),
      {
        token: pbToken,
        refresh_token: refreshPb,
        expiresAt: expiresAt,
        userId: userId,
      },
      TOKEN_TTL_SECONDS
    );

    // Store Graph token
    await cacheService.set<CachedToken>(
      CACHE_KEYS.TOKEN_GRAPH(userId),
      {
        token: graphToken,
        refresh_token: refreshGraph,
        expiresAt: expiresAt,
        userId: userId,
      },
      TOKEN_TTL_SECONDS
    );

    // Store refresh tokens with longer TTL
    await cacheService.set(
      CACHE_KEYS.REFRESH_PB(userId),
      refreshPb,
      REFRESH_TOKEN_TTL_SECONDS
    );

    await cacheService.set(
      CACHE_KEYS.REFRESH_GRAPH(userId),
      refreshGraph,
      REFRESH_TOKEN_TTL_SECONDS
    );

    // Track active user
    this.activeUsers.add(userId);
    await cacheService.sadd(CACHE_KEYS.ACTIVE_USERS, userId);

    console.log(`[TokenService] Tokens stored for user ${userId}`);
  }

  /**
   * Get fresh token for user (from cache or refresh)
   */
  async getToken(userId: string, type: TokenType): Promise<TokenRefreshResponse | null> {
    const cacheKey = type === 'pb' 
      ? CACHE_KEYS.TOKEN_PB(userId)
      : CACHE_KEYS.TOKEN_GRAPH(userId);

    // Try to get from cache
    const cached = await cacheService.get<CachedToken>(cacheKey);

    if (cached && !this.isExpiring(cached.expiresAt)) {
      return {
        token: cached.token,
        expiresAt: cached.expiresAt,
        source: 'cache',
        type,
      };
    }

    // Need to refresh
    const refreshed = await this.refreshToken(userId, type);
    if (refreshed) {
      return {
        token: refreshed.token,
        expiresAt: refreshed.expiresAt,
        source: 'refreshed',
        type,
      };
    }

    // Return stale token if available (better than nothing for field workers)
    if (cached) {
      console.warn(`[TokenService] Returning stale ${type} token for user ${userId}`);
      return {
        token: cached.token,
        expiresAt: cached.expiresAt,
        source: 'cache',
        type,
      };
    }

    return null;
  }

  /**
   * Refresh a specific token type
   */
  async refreshToken(userId: string, type: TokenType): Promise<CachedToken | null> {
    const refreshKey = type === 'pb'
      ? CACHE_KEYS.REFRESH_PB(userId)
      : CACHE_KEYS.REFRESH_GRAPH(userId);

    const refreshToken = await cacheService.get<string>(refreshKey);
    if (!refreshToken) {
      console.error(`[TokenService] No refresh token for ${type} user ${userId}`);
      return null;
    }

    try {
      let newToken: string;
      let newRefreshToken: string;

      if (type === 'pb') {
        const result = await pocketbaseService.refreshToken(refreshToken);
        newToken = result.token;
        newRefreshToken = result.refreshToken;
      } else {
        const result = await graphService.refreshToken(refreshToken);
        newToken = result.accessToken;
        newRefreshToken = result.refreshToken;
      }

      const expiresAt = Date.now() + TOKEN_TTL_SECONDS * 1000;
      const cachedToken: CachedToken = {
        token: newToken,
        refresh_token: newRefreshToken,
        expiresAt: expiresAt,
        userId: userId,
      };

      // Update cache
      const cacheKey = type === 'pb'
        ? CACHE_KEYS.TOKEN_PB(userId)
        : CACHE_KEYS.TOKEN_GRAPH(userId);

      await cacheService.set(cacheKey, cachedToken, TOKEN_TTL_SECONDS);
      await cacheService.set(refreshKey, newRefreshToken, REFRESH_TOKEN_TTL_SECONDS);

      console.log(`[TokenService] Refreshed ${type} token for user ${userId}`);
      return cachedToken;
    } catch (err) {
      console.error(`[TokenService] Failed to refresh ${type} token for user ${userId}:`, err);
      return null;
    }
  }

  /**
   * Start background refresh loop for a user
   */
  startRefreshLoop(userId: string): void {
    if (this.refreshLoops.has(userId)) {
      console.log(`[TokenService] Refresh loop already running for ${userId}`);
      return;
    }

    const loop = setInterval(async () => {
      await this.refreshUserTokens(userId);
    }, TOKEN_REFRESH_INTERVAL_MS);

    this.refreshLoops.set(userId, loop);
    this.activeUsers.add(userId);
    console.log(`[TokenService] Started refresh loop for user ${userId}`);
  }

  /**
   * Stop background refresh loop for a user
   */
  stopRefreshLoop(userId: string): void {
    const loop = this.refreshLoops.get(userId);
    if (loop) {
      clearInterval(loop);
      this.refreshLoops.delete(userId);
      this.activeUsers.delete(userId);
      console.log(`[TokenService] Stopped refresh loop for user ${userId}`);
    }
  }

  /**
   * Refresh all tokens for a user (called by background loop)
   */
  private async refreshUserTokens(userId: string): Promise<void> {
    console.log(`[TokenService] Background refresh for user ${userId}`);

    // Check PocketBase token
    const pbToken = await cacheService.get<CachedToken>(CACHE_KEYS.TOKEN_PB(userId));
    if (pbToken && this.isExpiring(pbToken.expiresAt)) {
      await this.refreshToken(userId, 'pb');
    }

    // Check Graph token
    const graphToken = await cacheService.get<CachedToken>(CACHE_KEYS.TOKEN_GRAPH(userId));
    if (graphToken && this.isExpiring(graphToken.expiresAt)) {
      await this.refreshToken(userId, 'graph');
    }
  }

  /**
   * Clear all tokens for a user (logout)
   */
  async clearUserTokens(userId: string): Promise<void> {
    this.stopRefreshLoop(userId);

    await Promise.all([
      cacheService.del(CACHE_KEYS.TOKEN_PB(userId)),
      cacheService.del(CACHE_KEYS.TOKEN_GRAPH(userId)),
      cacheService.del(CACHE_KEYS.REFRESH_PB(userId)),
      cacheService.del(CACHE_KEYS.REFRESH_GRAPH(userId)),
    ]);

    await cacheService.srem(CACHE_KEYS.ACTIVE_USERS, userId);
    console.log(`[TokenService] Cleared all tokens for user ${userId}`);
  }

  /**
   * Restore refresh loops for active users (on server restart)
   */
  async restoreRefreshLoops(): Promise<void> {
    const activeUsers = await cacheService.smembers(CACHE_KEYS.ACTIVE_USERS);
    
    for (const userId of activeUsers) {
      // Check if user still has valid tokens
      const pbToken = await cacheService.get<CachedToken>(CACHE_KEYS.TOKEN_PB(userId));
      if (pbToken) {
        this.startRefreshLoop(userId);
      } else {
        // Remove stale user
        await cacheService.srem(CACHE_KEYS.ACTIVE_USERS, userId);
      }
    }

    console.log(`[TokenService] Restored ${this.refreshLoops.size} refresh loops`);
  }

  /**
   * Get active user count
   */
  get activeUserCount(): number {
    return this.activeUsers.size;
  }
}

export const tokenService = new TokenService();
