import { useState, useEffect } from 'react';
import Sidebar from './components/Sidebar'
import MachineDetails from './components/MachineDetails'
import ActionPanel from './components/ActionPanel'
import LoginScreen from './components/LoginScreen'
import RestoreConnectionsModal from './components/RestoreConnectionsModal'
import { AddMachineModal } from './components/AddMachineModal'
import { SaveConfirmationModal } from './components/SaveConfirmationModal'
import { GuacamoleViewerWithSuspense, MachineCredentialsModalWithSuspense } from './components/LazyComponents'
import { ErrorBoundary } from './components/ErrorBoundary'
import { ToastProvider } from './components/Toast/ToastProvider'
import { BulkActionsToolbar } from './components/BulkActionsToolbar'
import { BulkHealthCheckProgressModal } from './components/BulkHealthCheckProgressModal'
import { BulkResultsModal } from './components/BulkResultsModal'
import { BulkSSHCommandModal } from './components/BulkSSHCommandModal'
import { BulkSSHResultsModal } from './components/BulkSSHResultsModal'
import { useMachineStore } from './store/machineStore'
import { authService } from './services/auth-service'
import { guacamoleService, type ActiveConnection } from './services/guacamole-api'
import { machineAvailabilityService } from './services/machine-availability'
import { bulkOperationsApi } from './services/bulk-operations-api'
import { websocketNotificationService, type WebSocketEvent } from './services/websocket-notifications'
import { log } from './utils/logger'
import { ErrorHandler } from './utils/errorHandler'
import { useToastHelpers } from './components/Toast/ToastProvider'
import { ProtocolOption } from './utils/protocolHelper'
import { Machine } from './types'

// Interface for connection data
interface MachineConnection {
  machineId: string;
  connectionUrl: string;
  connectionId: string;
  connectedAt: Date;
  expiresAt: Date;  // TTL expiration time from Redis
  ttlTimerId?: ReturnType<typeof setTimeout>;  // Timer ID for auto-disconnect
  enableSftp?: boolean;  // Flag to enable SFTP for SSH connections
}

function AppContent() {
  const selectedMachine = useMachineStore(state => state.selectedMachine);
  const machines = useMachineStore(state => state.machines);
  const savedMachines = useMachineStore(state => state.savedMachines); // For filtering saved-only operations
  const showAddMachineModal = useMachineStore(state => state.showAddMachineModal);
  const setShowAddMachineModal = useMachineStore(state => state.setShowAddMachineModal);
  const showSaveConfirmModal = useMachineStore(state => state.showSaveConfirmModal);
  const fetchSavedMachines = useMachineStore(state => state.fetchSavedMachines);
  const resetState = useMachineStore(state => state.resetState);
  
  const [isCredentialsModalOpen, setIsCredentialsModalOpen] = useState(false);
  
  // Store connections for each machine separately
  const [machineConnections, setMachineConnections] = useState<Map<string, MachineConnection>>(() => {
    // Restore connections from localStorage on load
    try {
      const saved = localStorage.getItem('machine-connections');
      if (saved) {
        const parsed = JSON.parse(saved);
        const restoredMap = new Map<string, MachineConnection>();
        
        for (const [machineId, conn] of parsed) {
          restoredMap.set(machineId, {
            ...conn,
            connectedAt: new Date(conn.connectedAt),
            expiresAt: new Date(conn.expiresAt),
            // ttlTimerId not restored - will be recreated separately
          });
        }
        
        log.info('app', 'Restored connections from localStorage', {
          count: restoredMap.size
        });
        
        return restoredMap;
      }
    } catch (error) {
      log.error('app', 'Failed to restore connections from localStorage', { error });
    }
    
    return new Map();
  });
  
  // Store machine availability statuses
  const [machineStatuses, setMachineStatuses] = useState<Map<string, Machine['status']>>(new Map());
  
  const [, setIsConnecting] = useState(false);
  const [currentUser, setCurrentUser] = useState(authService.getCurrentUser());
  const [tokenWarning, setTokenWarning] = useState(false);
  const [isAuthenticated, setIsAuthenticated] = useState(authService.isAuthenticated());
  
  // Bulk Operations State
  const [selectedMachineIds, setSelectedMachineIds] = useState<string[]>([]);
  const [bulkHealthCheckInProgress, setBulkHealthCheckInProgress] = useState(false);
  const [bulkHealthCheckProgress, setBulkHealthCheckProgress] = useState({
    total: 0,
    checked: 0,
    available: 0,
    unavailable: 0,
    currentMachine: undefined as string | undefined
  });
  const [bulkHealthCheckResults, setBulkHealthCheckResults] = useState<any>(null);
  const [showBulkResultsModal, setShowBulkResultsModal] = useState(false);
  
  // Bulk SSH Command State
  const [showSSHCommandModal, setShowSSHCommandModal] = useState(false);
  const [sshCommandResults, setSSHCommandResults] = useState<any>(null);
  const [showSSHResultsModal, setShowSSHResultsModal] = useState(false);
  
  // Restore Connections State
  const [showRestoreConnectionsModal, setShowRestoreConnectionsModal] = useState(false);
  const [activeConnections, setActiveConnections] = useState<ActiveConnection[]>([]);
  
  // WebSocket State
  const [_isWebSocketConnected, setIsWebSocketConnected] = useState(false);
  
  // Toast helpers for displaying errors
  const { error: showError, success: showSuccess, warning: showWarning } = useToastHelpers();

  // Get current connection for selected machine
  const currentConnection = selectedMachine ? machineConnections.get(selectedMachine.id) : null;

  // Function to get information about all connections (for debugging)
  // const _getConnectionsInfo = () => {
  //   const connections = Array.from(machineConnections.entries()).map(([machineId, conn]) => ({
  //     machineId,
  //     connectionId: conn.connectionId,
  //     connectedAt: conn.connectedAt.toISOString()
  //   }));
  //   
  //   log.info('app', 'Active connections summary', {
  //     totalConnections: connections.length,
  //     connections
  //   });
  //   
  //   return connections;
  // };

  // Save connections to localStorage on change
  useEffect(() => {
    try {
      // Convert Map to array for JSON storage
      const connectionsArray = Array.from(machineConnections.entries()).map(([machineId, conn]) => [
        machineId,
        {
          machineId: conn.machineId,
          connectionUrl: conn.connectionUrl,
          connectionId: conn.connectionId,
          connectedAt: conn.connectedAt.toISOString(),
          expiresAt: conn.expiresAt.toISOString(),
          // ttlTimerId not saved - will be recreated on restore
        }
      ]);
      
      localStorage.setItem('machine-connections', JSON.stringify(connectionsArray));
      
      if (machineConnections.size > 0) {
        log.debug('app', 'Machine connections saved to localStorage', {
          totalConnections: machineConnections.size,
          machineIds: Array.from(machineConnections.keys())
        });
      }
    } catch (error) {
      log.error('app', 'Failed to save connections to localStorage', { error });
    }
  }, [machineConnections]);

  useEffect(() => {
    // Check authentication on load
    setIsAuthenticated(authService.isAuthenticated());

    // Set token expiration handler
    authService.setOnTokenExpired(() => {
      log.warn('app', 'Token expired, forcing re-login');
      
      // Clear all TTL timers before clearing connections
      machineConnections.forEach((connection) => {
        if (connection.ttlTimerId) {
          clearTimeout(connection.ttlTimerId);
        }
      });
      
      // Disconnect WebSocket
      websocketNotificationService.disconnect();
      setIsWebSocketConnected(false);
      
      // CRITICAL: Clear all state on token expiration
      resetState();
      setCurrentUser(null);
      setMachineConnections(new Map()); // Clear all connections
      setMachineStatuses(new Map()); // Clear statuses
      setTokenWarning(false);
      setIsAuthenticated(false);
      
      // Clear localStorage
      localStorage.removeItem('machine-connections');
      localStorage.removeItem('machine-store');
    });

    // Set token warning handler
    authService.setOnTokenWarning((timeLeft: number) => {
      if (timeLeft <= 60) { // Show warning if less than a minute left
        setTokenWarning(true);
      }
    });
    
    // Cleanup all TTL timers on component unmount
    return () => {
      machineConnections.forEach((connection) => {
        if (connection.ttlTimerId) {
          clearTimeout(connection.ttlTimerId);
        }
      });
      
      // Disconnect WebSocket
      websocketNotificationService.disconnect();
      
      log.debug('app', 'Cleaned up all TTL timers and WebSocket on unmount');
    };
  }, [machineConnections]);

  // WebSocket Event Handlers
  useEffect(() => {
    if (!isAuthenticated) {
      return;
    }

    log.info('websocket', 'Setting up WebSocket event handlers');

    // WebSocket message handler (listen to all events)
    const unsubscribeMessages = websocketNotificationService.on('*', (event: WebSocketEvent) => {
      log.info('websocket', 'Received WebSocket event', { type: event.type });

      switch (event.type) {
        case 'connected': {
          log.info('websocket', 'WebSocket connected successfully');
          setIsWebSocketConnected(true);
          break;
        }

        case 'connection_expired': {
          const { connection_id, hostname, protocol } = event.data;
          
          log.warn('websocket', 'Connection expired notification received', {
            connection_id,
            hostname,
            protocol
          });

          // Find machine by connection_id and remove its connection
          const machineEntry = Array.from(machineConnections.entries()).find(
            ([, conn]) => conn.connectionId === connection_id
          );

          if (machineEntry) {
            const [machineId, connection] = machineEntry;
            
            // Clear timer if exists
            if (connection.ttlTimerId) {
              clearTimeout(connection.ttlTimerId);
            }

            // Remove connection
            setMachineConnections(prev => {
              const newMap = new Map(prev);
              newMap.delete(machineId);
              return newMap;
            });

            showWarning(
              'Connection Expired',
              `Your connection to ${hostname} (${protocol.toUpperCase()}) has expired.`
            );

            log.info('websocket', 'Removed expired connection', { machineId, connection_id });
          }
          break;
        }

        case 'connection_will_expire': {
          const { connection_id, hostname, protocol, minutes_remaining } = event.data;
          
          log.warn('websocket', 'Connection will expire warning received', {
            connection_id,
            hostname,
            protocol,
            minutes_remaining
          });

          showWarning(
            'Connection Expiring Soon',
            `Your connection to ${hostname} (${protocol.toUpperCase()}) will expire in ${minutes_remaining} minutes.`
          );
          break;
        }

        case 'connection_extended': {
          const { connection_id, hostname, new_expires_at, additional_minutes } = event.data;
          
          log.info('websocket', 'Connection extended notification received', {
            connection_id,
            hostname,
            new_expires_at,
            additional_minutes
          });

          // Update expiresAt for connection
          const machineEntry = Array.from(machineConnections.entries()).find(
            ([, conn]) => conn.connectionId === connection_id
          );

          if (machineEntry) {
            const [machineId] = machineEntry;
            
            setMachineConnections(prev => {
              const newMap = new Map(prev);
              const conn = newMap.get(machineId);
              if (conn) {
                conn.expiresAt = new Date(new_expires_at);
              }
              return newMap;
            });

            showSuccess(
              'Connection Extended',
              `Connection to ${hostname} extended by ${additional_minutes} minutes.`
            );

            log.info('websocket', 'Updated connection expiration time', { machineId, new_expires_at });
          }
          break;
        }

        case 'jwt_will_expire': {
          log.warn('websocket', 'JWT will expire soon', { data: event.data });
          showWarning(
            'Session Expiring Soon',
            `Your session will expire in ${event.data?.minutes_remaining || 5} minutes.`
          );
          break;
        }

        case 'jwt_expired': {
          log.error('websocket', 'JWT expired', { data: event.data });
          showError('Session Expired', 'Your session has expired. Please log in again.');
          break;
        }

        default:
          log.debug('websocket', 'Unhandled WebSocket event type', { type: event.type });
      }
    });

    // Cleanup on unmount or logout
    return () => {
      log.info('websocket', 'Cleaning up WebSocket event handlers');
      unsubscribeMessages();
    };
  }, [isAuthenticated, machineConnections, showWarning, showError, showSuccess]);

  // Periodic active connections check (every 30 seconds)
  useEffect(() => {
    if (!isAuthenticated || machineConnections.size === 0) {
      return;
    }

    log.info('app', 'Starting periodic connection check', { 
      connectionCount: machineConnections.size 
    });

    const interval = setInterval(() => {
      const now = new Date();
      
      machineConnections.forEach((connection, machineId) => {
        const timeUntilExpiry = connection.expiresAt.getTime() - now.getTime();
        const minutesUntilExpiry = Math.floor(timeUntilExpiry / 60000);

        log.debug('app', 'Checking connection expiry', {
          machineId,
          minutesUntilExpiry,
          expiresAt: connection.expiresAt.toISOString()
        });

        // If less than 0 minutes left - connection expired
        if (minutesUntilExpiry < 0) {
          log.warn('app', 'Connection expired locally (client-side check)', {
            machineId,
            connectionId: connection.connectionId,
          });

          // Clear timer if exists
          if (connection.ttlTimerId) {
            clearTimeout(connection.ttlTimerId);
          }

          // Remove connection
          setMachineConnections(prev => {
            const newMap = new Map(prev);
            newMap.delete(machineId);
            return newMap;
          });
        }
      });
    }, 30000); // 30 seconds

    return () => {
      log.info('app', 'Stopping periodic connection check');
      clearInterval(interval);
    };
  }, [isAuthenticated, machineConnections]);

  const handleLogin = async (username: string, password: string) => {
    try {
      // CRITICAL: Clear all previous data before new login
      // This protects from data leaks between users
      log.info('app', 'Clearing previous session before new login');
      
      // Clear TTL timers
      machineConnections.forEach((connection) => {
        if (connection.ttlTimerId) {
          clearTimeout(connection.ttlTimerId);
        }
      });
      
      // Clear store and state
      resetState();
      setMachineConnections(new Map());
      setMachineStatuses(new Map());
      setCurrentUser(null);
      
      log.info('app', 'Previous session cleared, proceeding with login');
      
      await authService.login(username, password);
      const user = authService.getCurrentUser();
      setCurrentUser(user);
      setIsAuthenticated(true);
      
      // Check user role and show appropriate notification
      if (user?.role === 'GUEST') {
        showWarning(
          'Limited Access',
          'You are logged in as GUEST. You can only view connections but cannot create new ones. Contact your administrator for USER role.'
        );
        log.warn('app', 'User logged in with GUEST role - limited access', { username: user.username });
      } else {
        showSuccess('Login successful', `Welcome, ${user?.username || 'User'}!`);
        log.info('app', 'User logged in successfully', { username: user?.username, role: user?.role });
      }
      
      // Load user's saved machines
      try {
        await fetchSavedMachines();
        log.info('app', 'Saved machines loaded successfully');
      } catch (err) {
        log.warn('app', 'Failed to load saved machines', { error: err });
        // Don't block login if machines fail to load
      }

      // Check active connections for restoration
      // Runs asynchronously, doesn't block login
      checkActiveConnections().catch(err => {
        log.error('app', 'Failed to check active connections', { error: err });
      });

      // Connect WebSocket for notifications
      try {
        const token = await authService.getToken();
        if (token) {
          await websocketNotificationService.connect(token);
          log.info('app', 'WebSocket connected successfully after login');
        } else {
          log.warn('app', 'No access token available for WebSocket connection');
        }
      } catch (err) {
        log.error('app', 'Failed to connect WebSocket', { error: err });
        // Don't block login if WebSocket fails to connect
      }
    } catch (error) {
      log.error('app', 'Login failed', { error });
      const appError = await ErrorHandler.handleApiError(error);
      showError('Login failed', appError.userMessage);
      throw error;
    }
  };

  const handleLogout = async () => {
    try {
      // Clear all TTL timers before logout
      machineConnections.forEach((connection) => {
        if (connection.ttlTimerId) {
          clearTimeout(connection.ttlTimerId);
        }
      });
      
      await authService.logout();
      
      // Disconnect WebSocket
      websocketNotificationService.disconnect();
      setIsWebSocketConnected(false);
      log.info('app', 'WebSocket disconnected on logout');
      
      // CRITICAL: Clear all state to prevent data leaks between users
      resetState();
      setCurrentUser(null);
      setMachineConnections(new Map()); // Clear all connections
      setMachineStatuses(new Map()); // Clear statuses
      setIsAuthenticated(false);
      
      // Clear localStorage
      localStorage.removeItem('machine-connections');
      localStorage.removeItem('machine-store');
      
      log.info('app', 'User logged out, all data cleared (security)');
    } catch (error) {
      log.error('app', 'Logout failed', { error });
    }
  };

  // Check active connections for restoration
  const checkActiveConnections = async () => {
    try {
      log.info('app', 'Checking for active connections to restore');
      
      const connections = await guacamoleService.getActiveConnections();
      
      if (connections.length > 0) {
        log.info('app', 'Found active connections', { 
          count: connections.length,
          connections: connections.map(c => ({
            id: c.connection_id,
            hostname: c.hostname,
            protocol: c.protocol,
            remaining_minutes: c.remaining_minutes
          }))
        });
        
        setActiveConnections(connections);
        setShowRestoreConnectionsModal(true);
      } else {
        log.info('app', 'No active connections to restore');
      }
    } catch (error) {
      log.error('app', 'Failed to check active connections', { error });
      // Don't show error to user - not critical
    }
  };

  // Restore connection
  const handleRestoreConnection = async (connection: ActiveConnection) => {
    try {
      log.info('app', 'Restoring connection', {
        connection_id: connection.connection_id,
        hostname: connection.hostname,
        protocol: connection.protocol
      });

      if (!connection.connection_url) {
        throw new Error('Connection URL is not available');
      }

      // Close modal window
      setShowRestoreConnectionsModal(false);

      // Open connection in new window (same as creating new)
      const connectionUrl = guacamoleService.getConnectionUrl(connection.connection_url);
      
      log.info('app', 'Opening restored connection', { 
        connectionUrl: connectionUrl.substring(0, 100) + '...' 
      });

      // IMPROVED: Find existing machine by hostname
      // First check by name, then by ip
      let existingMachine = machines.find(m => 
        m.name.toLowerCase() === connection.hostname.toLowerCase() ||
        m.ip.toLowerCase() === connection.hostname.toLowerCase()
      );

      // If machine not found, create temporary
      if (!existingMachine) {
        log.info('app', 'Machine not found in sidebar, creating temporary machine', {
          hostname: connection.hostname
        });

        existingMachine = {
          id: `restored-${connection.connection_id}`,
          name: connection.hostname,
          ip: connection.hostname,
          status: 'running',
          os: 'unknown',
          hypervisor: 'unknown',
          specs: {
            cpu: 'unknown',
            ram: 'unknown',
            disk: 'unknown'
          },
          testLinks: [],
          logs: []
        };
      } else {
        log.info('app', 'Found existing machine in sidebar', {
          machineId: existingMachine.id,
          machineName: existingMachine.name,
          hostname: connection.hostname
        });
      }

      // Create connection record
      const newConnection: MachineConnection = {
        machineId: existingMachine.id,
        connectionUrl: connection.connection_url,
        connectionId: connection.connection_id,
        connectedAt: new Date(connection.created_at),
        expiresAt: new Date(connection.expires_at)
      };

      // Add to Map
      setMachineConnections(prev => {
        const newMap = new Map(prev);
        newMap.set(existingMachine.id, newConnection);
        return newMap;
      });

      showSuccess(
        'Подключение восстановлено',
        `Восстановлено подключение к ${connection.hostname} (${connection.protocol.toUpperCase()}). Осталось ${connection.remaining_minutes} мин.`
      );

      log.info('app', 'Connection restored successfully', {
        connection_id: connection.connection_id,
        hostname: connection.hostname,
        machineId: existingMachine.id,
        usedExisting: existingMachine.id !== `restored-${connection.connection_id}`
      });
    } catch (error) {
      log.error('app', 'Failed to restore connection', { error });
      const appError = await ErrorHandler.handleApiError(error);
      showError('Ошибка восстановления', appError.userMessage);
    }
  };

  // Restore multiple connections
  const handleRestoreMultipleConnections = async (connections: ActiveConnection[]) => {
    if (connections.length === 0) {
      return;
    }

    log.info('app', 'Restoring multiple connections', {
      count: connections.length,
      connections: connections.map(c => ({ id: c.connection_id, hostname: c.hostname }))
    });

    // Close modal immediately
    setShowRestoreConnectionsModal(false);

    let successCount = 0;
    let failedCount = 0;

    // Restore connections sequentially
    for (const connection of connections) {
      try {
        log.info('app', 'Restoring connection', {
          connection_id: connection.connection_id,
          hostname: connection.hostname,
          protocol: connection.protocol
        });

        if (!connection.connection_url) {
          throw new Error('Connection URL is not available');
        }

        // Find existing machine by hostname
        let existingMachine = machines.find(m => 
          m.name.toLowerCase() === connection.hostname.toLowerCase() ||
          m.ip.toLowerCase() === connection.hostname.toLowerCase()
        );

        // If machine not found, create temporary
        if (!existingMachine) {
          existingMachine = {
            id: `restored-${connection.connection_id}`,
            name: connection.hostname,
            ip: connection.hostname,
            status: 'running',
            os: 'unknown',
            hypervisor: 'unknown',
            specs: {
              cpu: 'unknown',
              ram: 'unknown',
              disk: 'unknown'
            },
            testLinks: [],
            logs: []
          };
        }

        // Create connection record
        const newConnection: MachineConnection = {
          machineId: existingMachine.id,
          connectionUrl: connection.connection_url,
          connectionId: connection.connection_id,
          connectedAt: new Date(connection.created_at),
          expiresAt: new Date(connection.expires_at),
        };

        // Add to Map
        setMachineConnections(prev => {
          const newMap = new Map(prev);
          newMap.set(existingMachine.id, newConnection);
          return newMap;
        });

        successCount++;

        log.info('app', 'Connection restored successfully', {
          connection_id: connection.connection_id,
          hostname: connection.hostname,
          machineId: existingMachine.id
        });

        // Small delay between restorations
        await new Promise(resolve => setTimeout(resolve, 300));
      } catch (error) {
        failedCount++;
        log.error('app', 'Failed to restore connection', { 
          connection_id: connection.connection_id,
          hostname: connection.hostname,
          error 
        });
      }
    }

    // Show final notification
    if (successCount > 0 && failedCount === 0) {
      showSuccess(
        'Подключения восстановлены',
        `Успешно восстановлено ${successCount} ${successCount === 1 ? 'подключение' : 'подключений'}`
      );
    } else if (successCount > 0 && failedCount > 0) {
      showSuccess(
        'Частичное восстановление',
        `Восстановлено: ${successCount}, ошибок: ${failedCount}`
      );
    } else if (failedCount > 0) {
      showError(
        'Ошибка восстановления',
        `Не удалось восстановить ${failedCount} ${failedCount === 1 ? 'подключение' : 'подключений'}`
      );
    }

    log.info('app', 'Multiple connections restore completed', {
      total: connections.length,
      success: successCount,
      failed: failedCount
    });
  };

  // Check machine availability
  const checkMachineAvailability = async (machine: Machine) => {
    log.info('app', 'Checking machine availability', { 
      machineId: machine.id,
      machineName: machine.name,
      port: machine.port,
      isSavedMachine: machine.hypervisor === 'Saved'
    });

    // Set status to "checking"
    setMachineStatuses(prev => new Map(prev.set(machine.id, 'checking')));

    try {
      // For saved machines use explicit port, for others - auto-detect
      const portToCheck = machine.port; // undefined for mock machines, explicit port for saved
      
      // Perform check via API
      const result = await machineAvailabilityService.checkAvailability(machine, portToCheck);

      // Update status based on result
      const newStatus: Machine['status'] = result.available ? 'running' : 'stopped';
      setMachineStatuses(prev => new Map(prev.set(machine.id, newStatus)));

      log.info('app', 'Machine availability check completed', {
        machineId: machine.id,
        machineName: machine.name,
        port: result.port,
        available: result.available,
        responseTimeMs: result.responseTimeMs,
        newStatus
      });
    } catch (error) {
      log.error('app', 'Failed to check machine availability', {
        machineId: machine.id,
        machineName: machine.name,
        error
      });

      // On error set status to 'error'
      setMachineStatuses(prev => new Map(prev.set(machine.id, 'error')));
    }
  };

  const handleConnect = () => {
    if (!authService.isAuthenticated()) {
      setIsAuthenticated(false);
      return;
    }
    
    // Block connection for GUEST role
    if (currentUser?.role === 'GUEST') {
      showError(
        'Access Denied',
        'Your account has limited access (GUEST role). You can only view connections but cannot create new ones. Please contact your administrator to upgrade to USER role.'
      );
      log.warn('app', 'Connection attempt blocked for GUEST role', { 
        username: currentUser.username 
      });
      return;
    }
    
    if (selectedMachine) {
      setIsCredentialsModalOpen(true);
    }
  };

  // Smart connect handler from Sidebar
  const handleSmartConnect = (machine: Machine, protocol?: ProtocolOption) => {
    if (!authService.isAuthenticated()) {
      setIsAuthenticated(false);
      return;
    }
    
    // Block connection for GUEST role
    if (currentUser?.role === 'GUEST') {
      showError(
        'Access Denied',
        'Your account has limited access (GUEST role). You can only view connections but cannot create new ones. Please contact your administrator to upgrade to USER role.'
      );
      log.warn('app', 'Smart connection attempt blocked for GUEST role', { 
        username: currentUser.username,
        machineId: machine.id 
      });
      return;
    }
    
    // If already has active connection to this machine
    const existingConnection = machineConnections.get(machine.id);
    if (existingConnection) {
      showWarning('Already connected', `You are already connected to ${machine.name}`);
      return;
    }
    
    // Set machine as selected and open connection modal
    useMachineStore.getState().selectMachine(machine);
    setIsCredentialsModalOpen(true);
    
    if (protocol) {
      showSuccess('Protocol selected', `Using ${protocol.label} for ${machine.name}`);
      log.info('app', 'Smart connection initiated', {
        machineId: machine.id,
        protocol: protocol.value,
        description: protocol.description
      });
    }
  };

  const handleMachineConnect = async (
    username: string, 
    password: string, 
    protocol: string,
    sftpOptions?: { enableSftp: boolean; sftpRootDirectory: string }
  ) => {
    if (!selectedMachine) return;

    setIsConnecting(true);
    try {
      const response = await guacamoleService.createConnection(selectedMachine, {
        username,
        password,
        protocol,
        ...(sftpOptions && {
          enableSftp: sftpOptions.enableSftp,
          sftpRootDirectory: sftpOptions.sftpRootDirectory
        })
      });
      
      // Parse expires_at from ISO string to Date
      const expiresAt = new Date(response.expires_at);
      const now = new Date();
      const ttlMs = expiresAt.getTime() - now.getTime();
      
      log.info('app', 'Connection TTL setup', {
        machineId: selectedMachine.id,
        expiresAt: expiresAt.toISOString(),
        ttlMinutes: Math.round(ttlMs / 60000),
        ttlMs
      });
      
      // Start timer for automatic disconnect on TTL expiration
      const ttlTimerId = setTimeout(() => {
        log.info('app', 'Connection TTL expired, auto-disconnecting', {
          machineId: selectedMachine.id,
          connectionId: response.connection_id,
          expiresAt: expiresAt.toISOString()
        });
        
        // Remove connection (as if user clicked Disconnect)
        setMachineConnections(prev => {
          const newMap = new Map(prev);
          newMap.delete(selectedMachine.id);
          return newMap;
        });
        
        // Show notification
        showWarning(
          'Session expired',
          `Connection to ${selectedMachine.name} has expired after ${response.ttl_minutes} minutes`
        );
      }, ttlMs);
      
      // Save connection for specific machine
      const newConnection: MachineConnection = {
        machineId: selectedMachine.id,
        connectionUrl: response.connection_url,
        connectionId: response.connection_id,
        connectedAt: now,
        expiresAt: expiresAt,
        ttlTimerId: ttlTimerId,  // Save timer ID for cleanup
        enableSftp: sftpOptions?.enableSftp,  // Save SFTP flag for tooltips
      };

      setMachineConnections(prev => new Map(prev.set(selectedMachine.id, newConnection)));
      showSuccess(
        'Connected',
        `Connected to ${selectedMachine.name}. Session expires in ${response.ttl_minutes} minutes`
      );
      
      log.info('app', 'Connection established', {
        machineId: selectedMachine.id,
        connectionId: response.connection_id,
        expiresAt: expiresAt.toISOString()
      });
    } catch (error) {
      log.error('app', 'Failed to connect to machine', { error });
      const appError = await ErrorHandler.handleApiError(error);
      showError('Connection failed', appError.userMessage);
      throw error;
    } finally {
      setIsConnecting(false);
    }
  };

  const handleDisconnect = async () => {
    if (!selectedMachine) return;
    
    // Clear TTL timer if exists
    const connection = machineConnections.get(selectedMachine.id);
    if (connection?.ttlTimerId) {
      clearTimeout(connection.ttlTimerId);
      log.debug('app', 'TTL timer cleared for manual disconnect', {
        machineId: selectedMachine.id
      });
    }
    
    // CRITICAL: Delete connection from Redis and Guacamole via API
    if (connection) {
      try {
        await guacamoleService.deleteConnection(connection.connectionId);
        log.info('app', 'Connection deleted from server', {
          machineId: selectedMachine.id,
          connectionId: connection.connectionId
        });
      } catch (error) {
        log.error('app', 'Failed to delete connection from server', {
          machineId: selectedMachine.id,
          connectionId: connection.connectionId,
          error
        });
        // Continue client-side deletion even if API failed
      }
    }
    
    // Remove connection for specific machine from client state
    setMachineConnections(prev => {
      const newMap = new Map(prev);
      newMap.delete(selectedMachine.id);
      return newMap;
    });
    
    log.info('app', 'Disconnected from machine', {
      machineId: selectedMachine.id
    });
  };

  /**
   * Extend TTL of active connection
   */
  const handleExtendConnection = async (additionalMinutes: number = 60) => {
    if (!selectedMachine) return;
    
    const connection = machineConnections.get(selectedMachine.id);
    if (!connection) {
      showWarning('No Active Connection', 'There is no active connection to extend.');
      return;
    }
    
    try {
      log.info('app', 'Extending connection TTL', {
        machineId: selectedMachine.id,
        connectionId: connection.connectionId,
        additionalMinutes
      });
      
      const result = await guacamoleService.extendConnectionTTL(connection.connectionId, additionalMinutes);
      
      // Update expiresAt in state
      setMachineConnections(prev => {
        const newMap = new Map(prev);
        const conn = newMap.get(selectedMachine.id);
        if (conn) {
          conn.expiresAt = new Date(result.new_expires_at);
        }
        return newMap;
      });
      
      showSuccess(
        'Connection Extended',
        `Connection extended by ${result.additional_minutes} minutes.`
      );
      
      log.info('app', 'Connection TTL extended successfully', {
        machineId: selectedMachine.id,
        connectionId: connection.connectionId,
        newExpiresAt: result.new_expires_at
      });
    } catch (error) {
      log.error('app', 'Failed to extend connection TTL', {
        machineId: selectedMachine.id,
        connectionId: connection.connectionId,
        error
      });
      
      const appError = await ErrorHandler.handleApiError(error);
      showError('Failed to Extend Connection', appError.userMessage);
    }
  };

  // ========================================================================================
  // Bulk Operations Handlers
  // ========================================================================================

  // Handle bulk selection change from Sidebar
  const handleBulkSelectionChange = (machineIds: string[]) => {
    setSelectedMachineIds(machineIds);
    log.debug('app', 'Bulk selection changed', { count: machineIds.length });
  };

  // Clear bulk selection
  const clearBulkSelection = () => {
    setSelectedMachineIds([]);
    log.debug('app', 'Bulk selection cleared');
  };

  // Handle bulk health check
  const handleBulkHealthCheck = async () => {
    if (selectedMachineIds.length === 0) {
      showWarning('No Machines Selected', 'Please select machines to perform health check.');
      return;
    }

    // Check role limits
    const limits = bulkOperationsApi.getRoleLimits(currentUser?.role || 'GUEST');
    if (selectedMachineIds.length > limits.maxHealthCheck) {
      showError(
        'Selection Limit Exceeded',
        `Your role (${currentUser?.role}) can check max ${limits.maxHealthCheck} machines at once. Please reduce selection.`
      );
      return;
    }

    log.info('app', 'Starting bulk health check', {
      machineCount: selectedMachineIds.length,
      userRole: currentUser?.role
    });

    // Initialize progress
    setBulkHealthCheckProgress({
      total: selectedMachineIds.length,
      checked: 0,
      available: 0,
      unavailable: 0,
      currentMachine: undefined
    });
    setBulkHealthCheckInProgress(true);

    try {
      // Execute bulk health check
      const results = await bulkOperationsApi.bulkHealthCheck({
        machine_ids: selectedMachineIds,
        timeout: 5,
        check_port: true
      });

      log.info('app', 'Bulk health check completed', {
        total: results.total,
        available: results.available,
        unavailable: results.unavailable,
        executionTime: results.execution_time_ms
      });

      // Update progress to show completion
      setBulkHealthCheckProgress({
        total: results.total,
        checked: results.total,
        available: results.available,
        unavailable: results.unavailable,
        currentMachine: undefined
      });

      // Store results and show results modal
      setBulkHealthCheckResults(results);
      
      // Show completion notification
      showSuccess(
        'Health Check Complete',
        `Checked ${results.total} machines: ${results.available} available, ${results.unavailable} unavailable`
      );

      // Auto-open results modal after a short delay
      setTimeout(() => {
        setBulkHealthCheckInProgress(false);
        setShowBulkResultsModal(true);
        clearBulkSelection();
      }, 1500);

    } catch (error) {
      log.error('app', 'Bulk health check failed', { error });
      
      const errorDetails = await ErrorHandler.handleApiError(error);
      showError('Health Check Failed', errorDetails.userMessage);
      
      setBulkHealthCheckInProgress(false);
    }
  };

  // Retry failed health checks
  const handleRetryFailed = async () => {
    if (!bulkHealthCheckResults) return;
    
    const failedMachineIds = bulkHealthCheckResults.results
      .filter((r: any) => !r.available)
      .map((r: any) => r.machine_id);
    
    if (failedMachineIds.length === 0) {
      showWarning('No Failed Machines', 'All machines are available.');
      return;
    }

    log.info('app', 'Retrying failed health checks', { count: failedMachineIds.length });
    
    setSelectedMachineIds(failedMachineIds);
    setShowBulkResultsModal(false);
    
    // Trigger health check with failed machines
    setTimeout(() => handleBulkHealthCheck(), 500);
  };

  // ========================================================================================
  // Bulk SSH Command Handlers
  // ========================================================================================

  // Open SSH command modal
  const handleOpenSSHCommandModal = () => {
    if (selectedMachineIds.length === 0) {
      showWarning('No Machines Selected', 'Please select machines to execute SSH command.');
      return;
    }

    if (currentUser?.role === 'GUEST') {
      showError('Access Denied', 'GUEST role cannot execute SSH commands. Please contact your administrator.');
      return;
    }

    // Check role limits
    const limits = bulkOperationsApi.getRoleLimits(currentUser?.role || 'USER');
    if (selectedMachineIds.length > limits.maxSSHCommand) {
      showError(
        'Selection Limit Exceeded',
        `Your role (${currentUser?.role}) can execute commands on max ${limits.maxSSHCommand} machines at once.`
      );
      return;
    }

    setShowSSHCommandModal(true);
  };

  // Execute SSH command
  const handleExecuteSSHCommand = async (
    command: string,
    mode: 'global' | 'custom',
    globalCreds?: { username: string; password: string },
    customCreds?: { [machineId: string]: { username: string; password: string } }
  ) => {
    log.info('app', 'Starting bulk SSH command', {
      machineCount: selectedMachineIds.length,
      command: command.substring(0, 50),
      mode,
      userRole: currentUser?.role
    });

    setShowSSHCommandModal(false);

    try {
      // Collect hostnames for all machines
      const machineIdsToUse = selectedMachineIds;
      const machineHostnames: { [machineId: string]: string } = {};

      selectedMachineIds.forEach(id => {
        const machine = machines.find(m => m.id === id);
        if (machine) {
          // For saved machines use hostname from DB, for mock machines use IP
          const savedMachine = savedMachines.find(sm => sm.id === id);
          const hostname = savedMachine?.hostname || machine.ip || machine.name;
          machineHostnames[id] = hostname;
        }
      });

      const results = await bulkOperationsApi.bulkSSHCommand({
        machine_ids: machineIdsToUse,
        machine_hostnames: Object.keys(machineHostnames).length > 0 ? machineHostnames : undefined,
        command,
        credentials_mode: mode,
        global_credentials: globalCreds,
        machine_credentials: customCreds,
        timeout: 30
      });

      log.info('app', 'Bulk SSH command completed', {
        total: results.total,
        success: results.success,
        failed: results.failed
      });

      setSSHCommandResults(results);
      
      showSuccess(
        'SSH Command Complete',
        `Executed on ${results.total} machines: ${results.success} success, ${results.failed} failed`
      );

      // Show results immediately
      setShowSSHResultsModal(true);
      clearBulkSelection();

    } catch (error) {
      log.error('app', 'Bulk SSH command failed', { error });
      
      const errorDetails = await ErrorHandler.handleApiError(error);
      showError('SSH Command Failed', errorDetails.userMessage);
    }
  };

  // Retry failed SSH commands
  const handleRetryFailedSSH = async () => {
    if (!sshCommandResults) return;
    
    const failedMachineIds = sshCommandResults.results
      .filter((r: any) => r.status !== 'success')
      .map((r: any) => r.machine_id);
    
    if (failedMachineIds.length === 0) {
      showWarning('No Failed Machines', 'All machines executed successfully.');
      return;
    }

    log.info('app', 'Retrying failed SSH commands', { count: failedMachineIds.length });
    
    setSelectedMachineIds(failedMachineIds);
    setShowSSHResultsModal(false);
    
    setTimeout(() => handleOpenSSHCommandModal(), 500);
  };

  // If user not authenticated, show Login Screen
  if (!isAuthenticated) {
    return <LoginScreen onLogin={handleLogin} />;
  }

  return (
    <div className="flex h-screen bg-kaspersky-bg font-kaspersky">
      <Sidebar 
        onConnect={handleSmartConnect}
        machineStatuses={machineStatuses}
        onMachineSelected={checkMachineAvailability}
        machineConnections={machineConnections}
        userRole={currentUser?.role}
        onBulkSelectionChange={handleBulkSelectionChange}
      />
      <div className="flex-1 flex flex-col">
        {/* Header с информацией о пользователе */}
        <div className="bg-white shadow-sm px-6 py-3 flex justify-between items-center select-none">
          <div className="flex items-center gap-4">
            {currentUser && (
              <span className="text-sm text-gray-600">
                Logged in as: <strong>{currentUser.username}</strong> ({currentUser.role})
              </span>
            )}
            {machineConnections.size > 0 && (
              <div className="flex items-center gap-2 px-3 py-1 bg-green-100 border border-green-300 rounded-md">
                <svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
                </svg>
                <span className="text-sm text-green-700 font-medium">
                  {machineConnections.size} active connection{machineConnections.size !== 1 ? 's' : ''}
                </span>
              </div>
            )}
            {tokenWarning && (
              <div className="flex items-center gap-2 px-3 py-1 bg-yellow-100 border border-yellow-300 rounded-md">
                <svg className="w-4 h-4 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 18.5c-.77.833.192 2.5 1.732 2.5z" />
                </svg>
                <span className="text-sm text-yellow-700 font-medium">Session expires soon</span>
              </div>
            )}
          </div>
          <button
            onClick={handleLogout}
            className="text-sm text-red-600 hover:text-red-700 transition-colors select-none"
          >
            Logout
          </button>
        </div>

        {selectedMachine ? (
          <div className="flex-1 flex flex-col">
            <div className="flex-1 flex">
              {/* Machine Details Panel - 25% когда подключена консоль */}
              <div className={`${currentConnection ? 'w-1/4' : 'flex-1'} min-w-[320px] flex flex-col ${currentConnection ? 'border-r border-gray-200' : ''} transition-all duration-500`}>
                <MachineDetails 
                  machine={selectedMachine}
                  machineStatus={machineStatuses.get(selectedMachine.id)}
                  isConnected={!!currentConnection}
                  connectionInfo={currentConnection ? {
                    connectionId: currentConnection.connectionId,
                    connectedAt: currentConnection.connectedAt,
                    expiresAt: currentConnection.expiresAt
                  } : undefined}
                  onDelete={() => {
                    // After deletion reset selected machine
                    useMachineStore.getState().selectMachine(null);
                    showSuccess('Machine deleted', 'Saved machine has been successfully deleted');
                  }}
                  onExtendConnection={handleExtendConnection}
                />
              </div>
              
              {/* Connection Area - 75% экрана при подключении */}
              {machineConnections.size > 0 && (
                <div className="flex-1 flex flex-col animate-slide-in relative">
                  {/* Рендерим все активные подключения, но показываем только текущее */}
                  {Array.from(machineConnections.entries()).map(([machineId, connection]) => {
                    const isVisible = selectedMachine?.id === machineId;
                    const machine = useMachineStore.getState().machines.find(m => m.id === machineId);
                    
                    if (!machine) return null;
                    
                    return (
                      <div
                        key={`connection-${machineId}-${connection.connectionId}`}
                        className={`absolute inset-0 flex flex-col ${isVisible ? 'z-10' : 'z-0 pointer-events-none opacity-0'} transition-opacity duration-300`}
                      >
                        <GuacamoleViewerWithSuspense
                          machine={machine}
                          connectionUrl={connection.connectionUrl}
                          enableSftp={connection.enableSftp}
                          onError={(error: unknown) => {
                            log.error('app', 'Guacamole viewer error', { 
                              error, 
                              machineId: machine.id 
                            });
                            // Remove specific connection on error
                            setMachineConnections(prev => {
                              const newMap = new Map(prev);
                              newMap.delete(machine.id);
                              return newMap;
                            });
                          }}
                          onDisconnect={async () => {
                            // Get connection
                            const conn = machineConnections.get(machine.id);
                            
                            // Clear TTL timer if exists
                            if (conn?.ttlTimerId) {
                              clearTimeout(conn.ttlTimerId);
                            }
                            
                            // Delete from Redis and Guacamole via API
                            if (conn) {
                              try {
                                await guacamoleService.deleteConnection(conn.connectionId);
                                log.info('app', 'Connection deleted from server', {
                                  machineId: machine.id,
                                  connectionId: conn.connectionId
                                });
                              } catch (error) {
                                log.error('app', 'Failed to delete connection from server', {
                                  machineId: machine.id,
                                  error
                                });
                              }
                            }
                            
                            // Remove specific connection from client state
                            setMachineConnections(prev => {
                              const newMap = new Map(prev);
                              newMap.delete(machine.id);
                              return newMap;
                            });
                            
                            log.info('app', 'Disconnected from machine', {
                              machineId: machine.id
                            });
                          }}
                        />
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
            
            {/* Action Panel - всегда внизу, статично */}
            <ActionPanel 
              machine={selectedMachine} 
              onConnect={handleConnect}
              isConnected={!!currentConnection}
              onDisconnect={handleDisconnect}
              userRole={currentUser?.role}
            />
          </div>
        ) : (
          <div className="flex-1 flex items-center justify-center text-kaspersky-text select-none">
            <p>Select a machine from the sidebar</p>
          </div>
        )}
      </div>

      <MachineCredentialsModalWithSuspense
        isOpen={isCredentialsModalOpen}
        machine={selectedMachine}
        onClose={() => setIsCredentialsModalOpen(false)}
        onSubmit={handleMachineConnect}
      />

      <AddMachineModal
        isOpen={showAddMachineModal}
        onClose={() => setShowAddMachineModal(false)}
        onSuccess={() => {
          showSuccess('Machine saved', 'Machine has been successfully added to your profile');
          fetchSavedMachines(); // Обновляем список
        }}
      />

      <SaveConfirmationModal
        isOpen={showSaveConfirmModal}
        onClose={() => {}}
        onSuccess={() => {
          showSuccess('Machine saved', 'Machine has been successfully added to your profile');
          fetchSavedMachines(); // Обновляем список
        }}
      />

      {/* Restore Connections Modal */}
      <RestoreConnectionsModal
        isOpen={showRestoreConnectionsModal}
        connections={activeConnections}
        onRestore={handleRestoreConnection}
        onRestoreMultiple={handleRestoreMultipleConnections}
        onClose={() => setShowRestoreConnectionsModal(false)}
        onSkip={() => setShowRestoreConnectionsModal(false)}
      />

      {/* Bulk Operations Components */}
      <BulkActionsToolbar
        selectedCount={selectedMachineIds.length}
        onHealthCheck={handleBulkHealthCheck}
        onSSHCommand={handleOpenSSHCommandModal}
        onClearSelection={clearBulkSelection}
        userRole={currentUser?.role}
      />

      <BulkHealthCheckProgressModal
        isOpen={bulkHealthCheckInProgress}
        progress={bulkHealthCheckProgress}
        isComplete={bulkHealthCheckProgress.checked === bulkHealthCheckProgress.total && bulkHealthCheckProgress.total > 0}
        onClose={() => {
          setBulkHealthCheckInProgress(false);
          setShowBulkResultsModal(true);
        }}
      />

      <BulkResultsModal
        isOpen={showBulkResultsModal}
        results={bulkHealthCheckResults}
        onClose={() => {
          setShowBulkResultsModal(false);
          setBulkHealthCheckResults(null);
        }}
        onRetryFailed={handleRetryFailed}
      />

      {/* Bulk SSH Command Modals */}
      <BulkSSHCommandModal
        isOpen={showSSHCommandModal}
        selectedMachines={selectedMachineIds
          .map(id => machines.find(m => m.id === id))
          .filter((m): m is Machine => m !== undefined)}
        onClose={() => setShowSSHCommandModal(false)}
        onExecute={handleExecuteSSHCommand}
        userRole={currentUser?.role}
      />

      <BulkSSHResultsModal
        isOpen={showSSHResultsModal}
        results={sshCommandResults}
        onClose={() => {
          setShowSSHResultsModal(false);
          setSSHCommandResults(null);
        }}
        onRetryFailed={handleRetryFailedSSH}
      />
    </div>
  )
}

// Main application component with providers
function App() {
  return (
    <ToastProvider>
      <ErrorBoundary>
        <AppContent />
      </ErrorBoundary>
    </ToastProvider>
  );
}

export default App 