import { Logger } from 'pino';
import { randomUUID } from 'crypto';

export interface UserSession {
  userId: string;
  sessionId: string;
  connectionId: string;
  createdAt: number;
  lastActivity: number;
  metadata: {
    userAgent?: string;
    ipAddress?: string;
    extensionVersion?: string;
    [key: string]: any;
  };
}

export interface ExtensionConnection {
  connection: any;
  userId: string;
  sessionId: string;
  connectionId: string;
  connectedAt: number;
  lastActivity: number;
  isActive: boolean;
  metadata: any;
}

export interface PendingRequest {
  resolve: Function;
  reject: Function;
  userId: string;
  sessionId: string;
  createdAt: number;
  timeout: NodeJS.Timeout;
}

export class SessionManager {
  private logger: Logger;
  private userSessions: Map<string, UserSession> = new Map();
  private extensionConnections: Map<string, ExtensionConnection> = new Map();
  private sessionToConnection: Map<string, string> = new Map();
  private userToSessions: Map<string, Set<string>> = new Map();
  private pendingRequests: Map<string, PendingRequest> = new Map();
  private cleanupInterval: NodeJS.Timeout;

  constructor(logger: Logger) {
    this.logger = logger;

    // Start cleanup interval for stale sessions and connections
    this.cleanupInterval = setInterval(() => {
      this.cleanupStaleConnections();
      this.cleanupExpiredRequests();
    }, 30000); // Check every 30 seconds
  }

  /**
   * Generate a unique user ID
   */
  generateUserId(): string {
    return `user_${randomUUID()}`;
  }

  /**
   * Generate a unique session ID
   */
  generateSessionId(): string {
    return `session_${randomUUID()}`;
  }

  /**
   * Generate a unique connection ID
   */
  generateConnectionId(): string {
    return `conn_${randomUUID()}`;
  }

  /**
   * Register a new Chrome extension connection
   */
  registerExtensionConnection(
    connection: any,
    userId?: string,
    metadata: any = {},
  ): { userId: string; sessionId: string; connectionId: string } {
    const actualUserId = userId || this.generateUserId();
    const sessionId = this.generateSessionId();
    const connectionId = this.generateConnectionId();

    // Create user session
    const userSession: UserSession = {
      userId: actualUserId,
      sessionId,
      connectionId,
      createdAt: Date.now(),
      lastActivity: Date.now(),
      metadata: {
        userAgent: metadata.userAgent,
        ipAddress: metadata.ipAddress,
        extensionVersion: metadata.extensionVersion,
        ...metadata,
      },
    };

    // Create extension connection
    const extensionConnection: ExtensionConnection = {
      connection,
      userId: actualUserId,
      sessionId,
      connectionId,
      connectedAt: Date.now(),
      lastActivity: Date.now(),
      isActive: true,
      metadata,
    };

    // Store mappings
    this.userSessions.set(sessionId, userSession);
    this.extensionConnections.set(connectionId, extensionConnection);
    this.sessionToConnection.set(sessionId, connectionId);

    // Track user sessions
    if (!this.userToSessions.has(actualUserId)) {
      this.userToSessions.set(actualUserId, new Set());
    }
    this.userToSessions.get(actualUserId)!.add(sessionId);

    this.logger.info(
      `Extension registered - User: ${actualUserId}, Session: ${sessionId}, Connection: ${connectionId}`,
    );
    this.logConnectionStats();

    return { userId: actualUserId, sessionId, connectionId };
  }

  /**
   * Unregister a Chrome extension connection
   */
  unregisterExtensionConnection(connection: any): boolean {
    // Find connection by reference
    let connectionToRemove: ExtensionConnection | null = null;
    let connectionId: string | null = null;

    for (const [id, extConnection] of this.extensionConnections.entries()) {
      if (extConnection.connection === connection) {
        connectionToRemove = extConnection;
        connectionId = id;
        break;
      }
    }

    if (!connectionToRemove || !connectionId) {
      this.logger.warn('Attempted to unregister unknown connection');
      return false;
    }

    const { userId, sessionId } = connectionToRemove;

    // Remove from all mappings
    this.extensionConnections.delete(connectionId);
    this.sessionToConnection.delete(sessionId);
    this.userSessions.delete(sessionId);

    // Update user sessions
    const userSessions = this.userToSessions.get(userId);
    if (userSessions) {
      userSessions.delete(sessionId);
      if (userSessions.size === 0) {
        this.userToSessions.delete(userId);
      }
    }

    // Cancel any pending requests for this session
    this.cancelPendingRequestsForSession(sessionId);

    this.logger.info(
      `Extension unregistered - User: ${userId}, Session: ${sessionId}, Connection: ${connectionId}`,
    );
    this.logConnectionStats();

    return true;
  }

  /**
   * Get extension connection by session ID
   */
  getConnectionBySessionId(sessionId: string): ExtensionConnection | null {
    const connectionId = this.sessionToConnection.get(sessionId);
    if (!connectionId) {
      return null;
    }
    return this.extensionConnections.get(connectionId) || null;
  }

  /**
   * Get extension connection by user ID (returns first active connection)
   */
  getConnectionByUserId(userId: string): ExtensionConnection | null {
    const userSessions = this.userToSessions.get(userId);
    if (!userSessions || userSessions.size === 0) {
      return null;
    }

    // Find first active connection
    for (const sessionId of userSessions) {
      const connection = this.getConnectionBySessionId(sessionId);
      if (connection && connection.isActive) {
        return connection;
      }
    }

    return null;
  }

  /**
   * Get all active connections
   */
  getAllActiveConnections(): ExtensionConnection[] {
    return Array.from(this.extensionConnections.values()).filter((conn) => conn.isActive);
  }

  /**
   * Update last activity for a session
   */
  updateSessionActivity(sessionId: string): void {
    const session = this.userSessions.get(sessionId);
    if (session) {
      session.lastActivity = Date.now();
    }

    const connectionId = this.sessionToConnection.get(sessionId);
    if (connectionId) {
      const connection = this.extensionConnections.get(connectionId);
      if (connection) {
        connection.lastActivity = Date.now();
      }
    }
  }

  /**
   * Update user ID for an existing extension connection
   */
  updateExtensionUserId(connection: any, newUserId: string): any {
    // Find the extension connection
    let targetConnection: ExtensionConnection | null = null;
    let targetConnectionId: string | null = null;

    for (const [connectionId, extConnection] of this.extensionConnections.entries()) {
      if (extConnection.connection === connection) {
        targetConnection = extConnection;
        targetConnectionId = connectionId;
        break;
      }
    }

    if (!targetConnection || !targetConnectionId) {
      this.logger.warn('Extension connection not found for user ID update');
      return null;
    }

    const oldUserId = targetConnection.userId;
    const sessionId = targetConnection.sessionId;

    // Update the extension connection
    targetConnection.userId = newUserId;
    targetConnection.lastActivity = Date.now();

    // Update the user session
    const userSession = this.userSessions.get(sessionId);
    if (userSession) {
      userSession.userId = newUserId;
      userSession.lastActivity = Date.now();
    }

    // Update user to sessions mapping
    const oldUserSessions = this.userToSessions.get(oldUserId);
    if (oldUserSessions) {
      oldUserSessions.delete(sessionId);
      if (oldUserSessions.size === 0) {
        this.userToSessions.delete(oldUserId);
      }
    }

    if (!this.userToSessions.has(newUserId)) {
      this.userToSessions.set(newUserId, new Set());
    }
    this.userToSessions.get(newUserId)!.add(sessionId);

    this.logger.info(`Updated extension user ID from ${oldUserId} to ${newUserId}`);

    return {
      userId: newUserId,
      oldUserId: oldUserId,
      sessionId: sessionId,
      connectionId: targetConnectionId,
    };
  }

  /**
   * Store a pending request with session context
   */
  storePendingRequest(
    requestId: string,
    resolve: Function,
    reject: Function,
    sessionId: string,
    timeoutMs: number = 60000,
  ): void {
    const session = this.userSessions.get(sessionId);
    if (!session) {
      reject(new Error(`Session ${sessionId} not found`));
      return;
    }

    const timeout = setTimeout(() => {
      this.pendingRequests.delete(requestId);
      reject(new Error(`Request ${requestId} timed out after ${timeoutMs}ms`));
    }, timeoutMs);

    const pendingRequest: PendingRequest = {
      resolve,
      reject,
      userId: session.userId,
      sessionId,
      createdAt: Date.now(),
      timeout,
    };

    this.pendingRequests.set(requestId, pendingRequest);
  }

  /**
   * Resolve a pending request
   */
  resolvePendingRequest(requestId: string, result: any): boolean {
    const request = this.pendingRequests.get(requestId);
    if (!request) {
      return false;
    }

    clearTimeout(request.timeout);
    this.pendingRequests.delete(requestId);
    request.resolve(result);

    // Update session activity
    this.updateSessionActivity(request.sessionId);

    return true;
  }

  /**
   * Reject a pending request
   */
  rejectPendingRequest(requestId: string, error: any): boolean {
    const request = this.pendingRequests.get(requestId);
    if (!request) {
      return false;
    }

    clearTimeout(request.timeout);
    this.pendingRequests.delete(requestId);
    request.reject(error);

    return true;
  }

  /**
   * Cancel all pending requests for a session
   */
  private cancelPendingRequestsForSession(sessionId: string): void {
    const requestsToCancel: string[] = [];

    for (const [requestId, request] of this.pendingRequests.entries()) {
      if (request.sessionId === sessionId) {
        requestsToCancel.push(requestId);
      }
    }

    for (const requestId of requestsToCancel) {
      this.rejectPendingRequest(requestId, new Error(`Session ${sessionId} disconnected`));
    }

    this.logger.info(
      `Cancelled ${requestsToCancel.length} pending requests for session ${sessionId}`,
    );
  }

  /**
   * Clean up stale connections and sessions
   */
  private cleanupStaleConnections(): void {
    const now = Date.now();
    const staleThreshold = 5 * 60 * 1000; // 5 minutes
    const connectionsToRemove: string[] = [];

    for (const [connectionId, connection] of this.extensionConnections.entries()) {
      if (now - connection.lastActivity > staleThreshold) {
        connectionsToRemove.push(connectionId);
      }
    }

    for (const connectionId of connectionsToRemove) {
      const connection = this.extensionConnections.get(connectionId);
      if (connection) {
        this.logger.info(`Cleaning up stale connection: ${connectionId}`);
        this.unregisterExtensionConnection(connection.connection);
      }
    }
  }

  /**
   * Clean up expired requests
   */
  private cleanupExpiredRequests(): void {
    const now = Date.now();
    const expiredThreshold = 2 * 60 * 1000; // 2 minutes
    const requestsToRemove: string[] = [];

    for (const [requestId, request] of this.pendingRequests.entries()) {
      if (now - request.createdAt > expiredThreshold) {
        requestsToRemove.push(requestId);
      }
    }

    for (const requestId of requestsToRemove) {
      this.rejectPendingRequest(requestId, new Error('Request expired'));
    }

    if (requestsToRemove.length > 0) {
      this.logger.info(`Cleaned up ${requestsToRemove.length} expired requests`);
    }
  }

  /**
   * Log connection statistics
   */
  private logConnectionStats(): void {
    this.logger.info(
      `Connection Stats - Users: ${this.userToSessions.size}, Sessions: ${this.userSessions.size}, Connections: ${this.extensionConnections.size}, Pending Requests: ${this.pendingRequests.size}`,
    );
  }

  /**
   * Get session statistics
   */
  getStats(): any {
    return {
      totalUsers: this.userToSessions.size,
      totalSessions: this.userSessions.size,
      totalConnections: this.extensionConnections.size,
      activeConnections: this.getAllActiveConnections().length,
      pendingRequests: this.pendingRequests.size,
    };
  }

  /**
   * Cleanup resources
   */
  destroy(): void {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
    }

    // Cancel all pending requests
    for (const [requestId, request] of this.pendingRequests.entries()) {
      clearTimeout(request.timeout);
      request.reject(new Error('Session manager destroyed'));
    }

    this.pendingRequests.clear();
    this.extensionConnections.clear();
    this.userSessions.clear();
    this.sessionToConnection.clear();
    this.userToSessions.clear();
  }
}
