import { log } from '../utils/logger';
import { authService } from './auth-service';
import { Machine } from '../types';
import { API_CONFIG } from '../config/api';

const DEFAULT_TTL_MINUTES = 60;
const DEFAULT_EXTEND_MINUTES = 60;
const MIN_REMAINING_MINUTES = 0;

const HTTP_STATUS = {
  UNAUTHORIZED: 401,
  FORBIDDEN: 403,
  NOT_FOUND: 404,
} as const;

const SESSION_EXPIRED_PATTERNS = [
  'Session expired',
  'Invalid session',
  'encryption session has expired',
] as const;

const ERROR_MESSAGES = {
  SESSION_EXPIRED: 'Your session has expired. Please log in again.',
  CONNECTION_NOT_FOUND: 'Connection not found or already expired',
  PERMISSION_DENIED: 'You do not have permission to extend this connection',
} as const;

const PROTOCOL_SSH = 'ssh';
const DEFAULT_PROTOCOL = 'rdp';

interface ConnectionRequest {
  hostname: string;
  protocol: string;
  username?: string;
  password?: string;
  port?: number;
  ttl_minutes?: number;
  // SFTP parameters for SSH connections
  enable_sftp?: boolean;
  sftp_root_directory?: string;
  sftp_server_alive_interval?: number;
}

interface ConnectionResponse {
  connection_id: string;
  connection_url: string;
  status: string;
  expires_at: string;
  ttl_minutes: number;
}

interface ActiveConnection {
  connection_id: string;
  connection_url: string | null;
  hostname: string;
  protocol: string;
  owner_username: string;
  owner_role: string;
  created_at: string;
  expires_at: string;
  ttl_minutes: number;
  remaining_minutes: number;
  status: 'active' | 'expired';
}

interface ConnectionsListResponse {
  total_connections: number;
  active_connections: number;
  connections: ActiveConnection[];
}

class GuacamoleService {

  /**
   * Check if error detail indicates session expiry
   */
  private isSessionExpiredError(errorDetail?: string): boolean {
    if (!errorDetail) return false;
    return SESSION_EXPIRED_PATTERNS.some(pattern => 
      errorDetail.includes(pattern)
    );
  }

  /**
   * Handle 401 Unauthorized response
   * Checks for session expiry and logs out if needed
   */
  private async handle401Error(response: Response): Promise<void> {
    if (response.status !== HTTP_STATUS.UNAUTHORIZED) {
      return;
    }

    const errorData = await response.json().catch(() => ({}));
    
    if (this.isSessionExpiredError(errorData.detail)) {
      await authService.logout().catch(() => {});
      throw new Error(ERROR_MESSAGES.SESSION_EXPIRED);
    }
  }

  /**
   * Create connection to machine
   */
  async createConnection(
    machine: Machine, 
    credentials?: { 
      username: string; 
      password: string; 
      protocol?: string;
      enableSftp?: boolean;
      sftpRootDirectory?: string;
      sftpServerAliveInterval?: number;
    }
  ): Promise<ConnectionResponse> {
    try {
      log.info('guacamole-service', 'Creating connection', {
        machineId: machine.id,
        machineName: machine.name,
        ipAddress: machine.ip,
        hypervisor: machine.hypervisor,
        hostnameToUse: machine.hypervisor === 'Saved' ? machine.ip : machine.name
      });

      const protocol = credentials?.protocol || DEFAULT_PROTOCOL;
      
      const request: ConnectionRequest = {
        hostname: machine.hypervisor === 'Saved' ? machine.ip : machine.name,
        protocol: protocol,
        ttl_minutes: DEFAULT_TTL_MINUTES,
        ...(credentials?.username && { username: credentials.username }),
        ...(credentials?.password && { password: credentials.password }),
        // SFTP parameters for SSH (enabled by default for SSH)
        ...(protocol === PROTOCOL_SSH && {
          enable_sftp: credentials?.enableSftp !== undefined ? credentials.enableSftp : true,
          ...(credentials?.sftpRootDirectory && { sftp_root_directory: credentials.sftpRootDirectory }),
          ...(credentials?.sftpServerAliveInterval && { sftp_server_alive_interval: credentials.sftpServerAliveInterval })
        })
      };

      const headers: Record<string, string> = {
        'Content-Type': 'application/json',
        ...authService.getAuthHeaders()
      };

      // CSRF token NOT needed for JWT API
      // JWT Bearer tokens are not vulnerable to CSRF attacks

      log.info('guacamole-service', 'Sending request', {
        url: `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONNECTIONS.CREATE}`,
        method: 'POST',
        headers: Object.keys(headers),
        hasAuthHeader: !!headers['Authorization'],
        requestBody: request
      });

      const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONNECTIONS.CREATE}`, {
        method: 'POST',
        headers,
        body: JSON.stringify(request)
      });

      if (!response.ok) {
        // Handle 401 Unauthorized (Session expired)
        if (response.status === HTTP_STATUS.UNAUTHORIZED) {
        const errorData = await response.json().catch(() => ({}));
          log.error('guacamole-service', 'Session expired or invalid', {
            errorDetail: errorData.detail,
            machineId: machine.id
          });
          await this.handle401Error(response);
        }

        const errorData = await response.json().catch(() => ({}));
        
        log.error('guacamole-service', 'API request failed', {
          status: response.status,
          statusText: response.statusText,
          errorData,
          url: `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONNECTIONS.CREATE}`,
          requestBody: request
        });
        throw new Error(errorData.detail || `Failed to create connection: ${response.status} ${response.statusText}`);
      }

      const data: ConnectionResponse = await response.json();
      
      log.info('guacamole-service', 'Connection created successfully', {
        machineId: machine.id,
        connectionId: data.connection_id,
        expiresAt: data.expires_at
      });

      return data;
    } catch (error) {
      log.error('guacamole-service', 'Failed to create connection', {
        machineId: machine.id,
        error: error instanceof Error ? error.message : 'Unknown error'
      });
      throw error;
    }
  }

  /**
   * Get list of active connections
   */
  async listConnections(): Promise<any[]> {
    try {
      const headers: Record<string, string> = {
        ...authService.getAuthHeaders()
      };

      const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONNECTIONS.LIST}`, {
        method: 'GET',
        headers
      });

      if (!response.ok) {
        await this.handle401Error(response);
        throw new Error(`Failed to list connections: ${response.statusText}`);
      }

      const connections = await response.json();
      
      log.info('guacamole-service', 'Connections list retrieved', {
        count: connections.length
      });

      return connections;
    } catch (error) {
      log.error('guacamole-service', 'Failed to list connections', { error });
      throw error;
    }
  }

  /**
   * Get active connections for restoration
   * Returns only connections with 'active' status and valid connection_url
   */
  async getActiveConnections(): Promise<ActiveConnection[]> {
    try {
      log.info('guacamole-service', 'Fetching active connections for restoration');

      const headers: Record<string, string> = {
        ...authService.getAuthHeaders()
      };

      const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONNECTIONS.LIST}`, {
        method: 'GET',
        headers
      });

      if (!response.ok) {
        await this.handle401Error(response);
        throw new Error(`Failed to get active connections: ${response.statusText}`);
      }

      const data: ConnectionsListResponse = await response.json();
      
      // Filter only active connections with valid connection_url
      const activeConnections = data.connections.filter(
        conn => conn.status === 'active' && 
                conn.connection_url !== null && 
                conn.remaining_minutes > MIN_REMAINING_MINUTES
      );
      
      log.info('guacamole-service', 'Active connections retrieved', {
        total: data.total_connections,
        active: activeConnections.length,
        restorable: activeConnections.filter(c => c.connection_url).length
      });

      return activeConnections;
    } catch (error) {
      log.error('guacamole-service', 'Failed to get active connections', { 
        error: error instanceof Error ? error.message : 'Unknown error' 
      });
      throw error;
    }
  }

  /**
   * Delete connection
   */
  async deleteConnection(connectionId: string): Promise<void> {
    try {
      const headers: Record<string, string> = {
        ...authService.getAuthHeaders()
      };

      const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONNECTIONS.DELETE(connectionId)}`, {
        method: 'DELETE',
        headers
      });

      if (!response.ok) {
        await this.handle401Error(response);
        throw new Error(`Failed to delete connection: ${response.statusText}`);
      }

      log.info('guacamole-service', 'Connection deleted', { connectionId });
    } catch (error) {
      log.error('guacamole-service', 'Failed to delete connection', { connectionId, error });
      throw error;
    }
  }

  /**
   * Extend TTL of active connection
   */
  async extendConnectionTTL(connectionId: string, additionalMinutes: number = DEFAULT_EXTEND_MINUTES): Promise<{ new_expires_at: string; additional_minutes: number }> {
    try {
      const headers: Record<string, string> = {
        'Content-Type': 'application/json',
        ...authService.getAuthHeaders()
      };

      const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONNECTIONS.EXTEND(connectionId)}`, {
        method: 'POST',
        headers,
        body: JSON.stringify({ additional_minutes: additionalMinutes })
      });

      if (!response.ok) {
        await this.handle401Error(response);
        
        if (response.status === HTTP_STATUS.NOT_FOUND) {
          throw new Error(ERROR_MESSAGES.CONNECTION_NOT_FOUND);
        }
        
        if (response.status === HTTP_STATUS.FORBIDDEN) {
          throw new Error(ERROR_MESSAGES.PERMISSION_DENIED);
        }
        
        throw new Error(`Failed to extend connection: ${response.statusText}`);
      }

      const data = await response.json();
      
      log.info('guacamole-service', 'Connection TTL extended', { 
        connectionId, 
        additionalMinutes,
        newExpiresAt: data.new_expires_at 
      });
      
      return {
        new_expires_at: data.new_expires_at,
        additional_minutes: data.additional_minutes
      };
    } catch (error) {
      log.error('guacamole-service', 'Failed to extend connection TTL', { connectionId, additionalMinutes, error });
      throw error;
    }
  }

  /**
   * Get connection URL
   * This method now simply returns the URL from API response
   */
  getConnectionUrl(connectionUrl: string): string {
    return connectionUrl;
  }
}

export const guacamoleService = new GuacamoleService();
export default guacamoleService;
export type { ActiveConnection, ConnectionResponse, ConnectionsListResponse }; 