class IDRAC::Session

def base_url

def base_url
  protocol = use_ssl ? 'https' : 'http'
  "#{protocol}://#{host}:#{port}"
end

def basic_auth_headers(content_type = 'application/json')

def basic_auth_headers(content_type = 'application/json')
  {
    'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}",
    'Content-Type' => content_type
  }
end

def connection

def connection
  @connection ||= Faraday.new(url: base_url, ssl: { 
    verify: verify_ssl
    # Keep SSL settings minimal for cross-version compatibility
  }) do |faraday|
    faraday.request :multipart
    faraday.request :url_encoded
    faraday.adapter Faraday.default_adapter
    # Add request/response logging
    if @verbosity > 0
      faraday.response :logger, Logger.new(STDOUT), bodies: @verbosity >= 2 do |logger|
        logger.filter(/(Authorization: Basic )([^,\n]+)/, '\1[FILTERED]')
        logger.filter(/(Password"=>"?)([^,"]+)/, '\1[FILTERED]')
      end
    end
  end
end

def create

Create a Redfish session
def create
  # Skip if we're in direct mode
  if @direct_mode
    debug "Skipping Redfish session creation (direct mode)", 1, :light_yellow
    return false
  end
  
  # Determine the correct session endpoint based on Redfish version
  session_endpoint = determine_session_endpoint
  
  payload = { "UserName" => username, "Password" => password }
  
  debug "Attempting to create Redfish session at #{base_url}#{session_endpoint}", 1
  debug "SSL verification: #{verify_ssl ? 'Enabled' : 'Disabled'}", 1
  print_connection_debug_info if @verbosity >= 2
  
  # Try creation methods in sequence
  return true if create_session_with_content_type(session_endpoint, payload)
  return true if create_session_with_basic_auth(session_endpoint, payload)
  return true if handle_max_sessions_and_retry(session_endpoint, payload)
  return true if create_session_with_form_urlencoded(session_endpoint, payload)
  
  # If all attempts fail, switch to direct mode
  @direct_mode = true
  false
end

def create_session_with_basic_auth(url, payload)

def create_session_with_basic_auth(url, payload)
  begin
    debug "Creating session with Basic Auth", 1
    
    # Try first with JSON format
    response = request_with_basic_auth(:post, url, payload.to_json, 'application/json')
    
    debug "Response status: #{response.status}", 1
    debug "Response body size: #{response.body.to_s.size} bytes", 2
    
    if @verbosity >= 2 || response.status >= 400
      debug "Response body (first 500 chars): #{response.body.to_s[0..500]}", 2
    end
    
    if process_session_response(response)
      debug "Redfish session created successfully with Basic Auth (JSON)", 1, :green
      return true
    end
    
    # If that fails, try with form-urlencoded
    if response.status == 415 || (response.body.to_s.include?("unsupported media type"))
      debug "415 Unsupported Media Type with JSON, trying form-urlencoded", 1, :yellow
      
      form_data = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
      form_response = request_with_basic_auth(:post, url, form_data, 'application/x-www-form-urlencoded')
      
      if process_session_response(form_response)
        debug "Redfish session created successfully with Basic Auth (form-urlencoded)", 1, :green
        return true
      elsif form_response.status == 400
        # Check for maximum sessions error
        if (form_response.body.include?("maximum number of user sessions") || 
            form_response.body.include?("RAC0218") || 
            form_response.body.include?("Internal Server Error"))
          debug "Maximum sessions reached detected during session creation", 1, :light_red
          @sessions_maxed = true
          return false
        end
      end
    elsif response.status == 400
      # Check for maximum sessions error
      if (response.body.include?("maximum number of user sessions") || 
          response.body.include?("RAC0218") || 
          response.body.include?("Internal Server Error"))
        debug "Maximum sessions reached detected during session creation", 1, :light_red
        @sessions_maxed = true
        return false
      end
    end
    
    # Try one more approach with no Content-Type header
    debug "Trying Basic Auth with no Content-Type header", 1, :yellow
    no_content_type_response = connection.post(url) do |req|
      req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
      req.headers['Accept'] = '*/*'
      req.body = payload.to_json
    end
    
    if process_session_response(no_content_type_response)
      debug "Redfish session created successfully with Basic Auth (no content type)", 1, :green
      return true
    end
    
    debug "Failed to create Redfish session: #{response.status} - #{response.body}", 1, :red
    return false
  rescue Faraday::SSLError => e
    debug "SSL Error in Basic Auth request: #{e.message}", 1, :red
    debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
    debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
    return false
  rescue => e
    debug "Error during Redfish session creation with Basic Auth: #{e.class.name}: #{e.message}", 1, :red
    debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
    return false
  end
end

def create_session_with_content_type(url, payload)

def create_session_with_content_type(url, payload)
  begin
    debug "Creating session with Content-Type: application/json", 1
    
    response = connection.post(url) do |req|
      req.headers['Content-Type'] = 'application/json'
      req.headers['Accept'] = 'application/json'
      req.body = payload.to_json
      debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
      debug "Request body: #{req.body}", 2
    end
    
    debug "Response status: #{response.status}", 1
    debug "Response headers: #{response.headers.to_json}", 2
    debug "Response body: #{response.body}", 2
    
    if response.status == 405
      debug "405 Method Not Allowed: Check if the endpoint supports POST requests and verify the request format.", 1, :red
      return false
    end
    
    if process_session_response(response)
      debug "Redfish session created successfully", 1, :green
      return true
    end
    
    # If the response status is 415 (Unsupported Media Type), try with different Content-Type
    if response.status == 415 || (response.body.to_s.include?("unsupported media type"))
      debug "415 Unsupported Media Type, trying alternate content type", 1, :yellow
      
      # Try with no content-type header, just the payload
      alt_response = connection.post(url) do |req|
        # No Content-Type header
        req.headers['Accept'] = '*/*'
        req.body = payload.to_json
      end
      
      if process_session_response(alt_response)
        debug "Redfish session created successfully with alternate content type", 1, :green
        return true
      end
    end
  rescue Faraday::SSLError => e
    debug "SSL Error: #{e.message}", 1, :red
    debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
    debug "Connection URL: #{base_url}#{url}", 1
    debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
    return false
  rescue => e
    debug "First session creation attempt failed: #{e.class.name}: #{e.message}", 1, :light_red
    debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
  end
  false
end

def create_session_with_form_urlencoded(url, payload)

def create_session_with_form_urlencoded(url, payload)
  # Only try with form-urlencoded if we had a 415 error previously
  begin
    debug "Trying with form-urlencoded content type", 1
    debug "URL: #{base_url}#{url}", 1
    
    # Try first without any authorization header
    response = connection.post(url) do |req|
      req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
      req.headers['Accept'] = '*/*'
      req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
      debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
    end
    
    debug "Response status: #{response.status}", 1
    debug "Response headers: #{response.headers.to_json}", 2
    debug "Response body: #{response.body}", 3
    
    if process_session_response(response)
      debug "Redfish session created successfully with form-urlencoded", 1, :green
      return true
    end
    
    # If that fails, try with Basic Auth + form-urlencoded
    debug "Trying form-urlencoded with Basic Auth", 1
    auth_response = request_with_basic_auth(:post, url, "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}", 'application/x-www-form-urlencoded')
    
    if process_session_response(auth_response)
      debug "Redfish session created successfully with form-urlencoded + Basic Auth", 1, :green
      return true
    end
    
    # Last resort: try with both headers (some iDRAC versions need this)
    debug "Trying with both Content-Type headers", 1
    both_response = connection.post(url) do |req|
      req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
      req.headers['Accept'] = 'application/json'
      req.headers['X-Requested-With'] = 'XMLHttpRequest'
      req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
      req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
    end
    
    if process_session_response(both_response)
      debug "Redfish session created successfully with multiple content types", 1, :green
      return true
    else
      debug "Failed with form-urlencoded too: #{response.status} - #{response.body}", 1, :red
      return false
    end
  rescue Faraday::SSLError => e
    debug "SSL Error in form-urlencoded request: #{e.message}", 1, :red
    debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
    debug "Connection URL: #{base_url}#{url}", 1
    debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
    return false
  rescue => e
    debug "Error during form-urlencoded session creation: #{e.class.name}: #{e.message}", 1, :red
    debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
    return false
  end
end

def delete

Delete the Redfish session
def delete
  return false unless @x_auth_token || @session_location
  
  begin
    debug "Deleting Redfish session...", 1
    
    if @session_location
      # Use the X-Auth-Token for authentication
      headers = { 'X-Auth-Token' => @x_auth_token }
      
      begin
        response = connection.delete(@session_location) do |req|
          req.headers.merge!(headers)
        end
        
        if response.status == 200 || response.status == 204
          debug "Redfish session deleted successfully", 1, :green
          @x_auth_token = nil
          @session_location = nil
          return true
        end
      rescue => session_e
        debug "Error during session deletion via location: #{session_e.message}", 1, :yellow
        # Continue to try basic auth method
      end
    end
    
    # If deleting via session location fails or there's no session location,
    # try to delete by using the basic auth method
    if @x_auth_token
      # Try to determine session ID from the X-Auth-Token or session_location
      session_id = nil
      
      # Extract session ID from location if available
      if @session_location
        if @session_location =~ /\/([^\/]+)$/
          session_id = $1
        end
      end
      
      # If we have an extracted session ID
      if session_id
        debug "Trying to delete session by ID #{session_id}", 1
        
        begin
          endpoint = determine_session_endpoint
          delete_url = "#{endpoint}/#{session_id}"
          
          delete_response = request_with_basic_auth(:delete, delete_url, nil)
          
          if delete_response.status == 200 || delete_response.status == 204
            debug "Successfully deleted session via ID", 1, :green
            @x_auth_token = nil
            @session_location = nil
            return true
          end
        rescue => id_e
          debug "Error during session deletion via ID: #{id_e.message}", 1, :yellow
        end
      end
      
      # Last resort: clear the token variable even if we couldn't properly delete it
      debug "Clearing session token internally", 1, :yellow
      @x_auth_token = nil
      @session_location = nil
    end
    
    return false
  rescue => e
    debug "Error during Redfish session deletion: #{e.message}", 1, :red
    # Clear token variable anyway
    @x_auth_token = nil
    @session_location = nil
    return false
  end
end

def delete_all_sessions_with_basic_auth

Delete all sessions using Basic Authentication
def delete_all_sessions_with_basic_auth
  debug "Attempting to delete all sessions using Basic Authentication...", 1
  
  # First, get the list of sessions
  sessions_url = determine_session_endpoint
  
  begin
    # Get the list of sessions
    response = request_with_basic_auth(:get, sessions_url, nil, 'application/json')
    
    if response.status != 200
      debug "Failed to get sessions list: #{response.status} - #{response.body}", 1, :red
      # If we received HTML error, assume we can't get sessions and try direct session deletion
      if response.headers['content-type']&.include?('text/html') || response.body.to_s.include?('DOCTYPE html')
        debug "Received HTML error response, trying direct session deletion", 1, :light_yellow
        return try_delete_latest_sessions
      end
      return false
    end
    
    # Parse the response to get session IDs
    begin
      sessions_data = JSON.parse(response.body)
      
      if sessions_data['Members'] && sessions_data['Members'].any?
        debug "Found #{sessions_data['Members'].count} active sessions", 1, :light_yellow
        
        # Delete each session
        success = true
        sessions_data['Members'].each do |session|
          session_url = session['@odata.id']
          
          # Skip if no URL
          next unless session_url
          
          # Delete the session
          delete_response = request_with_basic_auth(:delete, session_url, nil, 'application/json')
          
          if delete_response.status == 200 || delete_response.status == 204
            debug "Successfully deleted session: #{session_url}", 1, :green
          else
            debug "Failed to delete session #{session_url}: #{delete_response.status}", 1, :red
            success = false
          end
          
          # Small delay between deletions
          sleep(1)
        end
        
        return success
      else
        debug "No active sessions found", 1, :light_yellow
        return true
      end
    rescue JSON::ParserError => e
      debug "Error parsing sessions response: #{e.message}", 1, :red
      debug "Trying direct session deletion", 1, :light_yellow
      return try_delete_latest_sessions
    end
  rescue => e
    debug "Error during session deletion with Basic Auth: #{e.message}", 1, :red
    debug "Trying direct session deletion", 1, :light_yellow
    return try_delete_latest_sessions
  end
end

def determine_session_endpoint

Determine the correct session endpoint based on Redfish version
def determine_session_endpoint
  begin
    debug "Checking Redfish version to determine session endpoint...", 1
    
    response = connection.get('/redfish/v1') do |req|
      req.headers['Accept'] = 'application/json'
    end
    
    if response.status == 200
      begin
        data = JSON.parse(response.body)
        redfish_version = data['RedfishVersion']
        
        if redfish_version
          debug "Detected Redfish version: #{redfish_version}", 1
          
          # For version 1.17.0 and below, use the /redfish/v1/Sessions endpoint
          # For newer versions, use /redfish/v1/SessionService/Sessions
          if Gem::Version.new(redfish_version) <= Gem::Version.new('1.17.0')
            endpoint = '/redfish/v1/Sessions'
            debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
            return endpoint
          else
            endpoint = '/redfish/v1/SessionService/Sessions'
            debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
            return endpoint
          end
        end
      rescue JSON::ParserError => e
        debug "Error parsing Redfish version: #{e.message}", 1, :red
        debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
      rescue => e
        debug "Error determining Redfish version: #{e.message}", 1, :red
        debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
      end
    end
  rescue => e
    debug "Error checking Redfish version: #{e.message}", 1, :red
    debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
  end
  
  # Default to /redfish/v1/Sessions if we can't determine version
  default_endpoint = '/redfish/v1/Sessions'
  debug "Defaulting to endpoint #{default_endpoint}", 1, :light_yellow
  default_endpoint
end

def force_clear_sessions

Force clear all sessions by directly using Basic Auth
def force_clear_sessions
  debug "Attempting to force clear all sessions...", 1
  
  max_retries = 3
  retry_count = 0
  
  while retry_count < max_retries
    if delete_all_sessions_with_basic_auth
      debug "Successfully cleared sessions using Basic Auth", 1, :green
      return true
    else
      retry_count += 1
      if retry_count < max_retries
        # Exponential backoff
        sleep_time = 2 ** retry_count
        debug "Retrying session clear after #{sleep_time} seconds (attempt #{retry_count+1}/#{max_retries})", 1, :light_yellow
        sleep(sleep_time)
      else
        debug "Failed to clear sessions after #{max_retries} attempts", 1, :red
        return false
      end
    end
  end
  
  false
end

def handle_max_sessions_and_retry(url, payload)

def handle_max_sessions_and_retry(url, payload)
  return false unless @sessions_maxed
  
  debug "Maximum sessions reached, attempting to clear sessions", 1
  if @auto_delete_sessions
    if force_clear_sessions
      debug "Successfully cleared sessions, trying to create a new session", 1, :green
      
      # Give the iDRAC a moment to process the session deletions
      sleep(3)
      
      # Try one more time after clearing with form-urlencoded
      begin
        response = connection.post(url) do |req|
          req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
          req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
          req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
        end
        
        if process_session_response(response)
          debug "Redfish session created successfully after clearing sessions", 1, :green
          return true
        else
          debug "Failed to create Redfish session after clearing sessions: #{response.status} - #{response.body}", 1, :red
          # If still failing, try direct mode
          debug "Falling back to direct mode", 1, :light_yellow
          @direct_mode = true
          return false
        end
      rescue => e
        debug "Error during session creation after clearing: #{e.class.name}: #{e.message}", 1, :red
        debug "Falling back to direct mode", 1, :light_yellow
        @direct_mode = true
        return false
      end
    else
      debug "Failed to clear sessions, switching to direct mode", 1, :light_yellow
      @direct_mode = true
      return false
    end
  else
    debug "Auto delete sessions is disabled, switching to direct mode", 1, :light_yellow
    @direct_mode = true
    return false
  end
end

def initialize(client)

def initialize(client)
  @client = client
  @host = client.host
  @username = client.username
  @password = client.password
  @port = client.port
  @use_ssl = client.use_ssl
  @verify_ssl = client.verify_ssl
  @x_auth_token = nil
  @session_location = nil
  @direct_mode = client.direct_mode
  @sessions_maxed = false
  @auto_delete_sessions = client.auto_delete_sessions
  @verbosity = client.respond_to?(:verbosity) ? client.verbosity : 0
end

def print_connection_debug_info

def print_connection_debug_info
  begin
    debug "=== Connection Debug Info ===", 2, :yellow
    debug "Host: #{host}, Port: #{port}, SSL: #{use_ssl}", 2
    debug "Ruby version: #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}", 2
    
    begin
      debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 2
    rescue => e
      debug "Could not determine OpenSSL version: #{e.message}", 2
    end
    
    # Test basic TCP connection first
    begin
      socket = TCPSocket.new(host, port)
      debug "TCP connection successful", 2, :green
      socket.close
    rescue => e
      debug "TCP connection failed: #{e.class.name}: #{e.message}", 2, :red
      debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
    end
    
    # Try SSL connection if using SSL
    if use_ssl
      begin
        tcp_client = TCPSocket.new(host, port)
        ssl_context = OpenSSL::SSL::SSLContext.new
        ssl_context.verify_mode = verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
        ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
        ssl_client.connect
        debug "SSL connection successful", 2, :green
        debug "SSL protocol: #{ssl_client.ssl_version}", 2
        debug "SSL cipher: #{ssl_client.cipher.join(', ')}", 2
        
        if @verbosity >= 3
          cert = ssl_client.peer_cert
          if cert
            debug "Server certificate:", 3
            debug "  Subject: #{cert.subject}", 3
            debug "  Issuer: #{cert.issuer}", 3
            debug "  Validity: #{cert.not_before} to #{cert.not_after}", 3
            debug "  Fingerprint: #{OpenSSL::Digest::SHA256.new(cert.to_der).to_s}", 3
          else
            debug "No server certificate available", 3, :yellow
          end
        end
        
        ssl_client.close
        tcp_client.close
      rescue => e
        debug "SSL connection failed: #{e.class.name}: #{e.message}", 2, :red
        debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
      end
    end
    debug "===========================", 2, :yellow
  rescue => e
    debug "Error during connection debugging: #{e.class.name}: #{e.message}", 2, :red
    debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
  end
end

def process_session_response(response)

def process_session_response(response)
  if response.status == 201 || response.status == 200
    @x_auth_token = response.headers['X-Auth-Token']
    @session_location = response.headers['Location']
    @sessions_maxed = false
    true
  else
    false
  end
end

def request_with_basic_auth(method, url, body = nil, content_type = 'application/json')

def request_with_basic_auth(method, url, body = nil, content_type = 'application/json')
  debug "Basic Auth request: #{method.to_s.upcase} #{url}", 1
  debug "Request body size: #{body.to_s.size} bytes", 2 if body
  
  connection.send(method, url) do |req|
    req.headers.merge!(basic_auth_headers(content_type))
    req.body = body if body
    debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
  end
rescue Faraday::SSLError => e
  debug "SSL Error in Basic Auth request: #{e.message}", 1, :red
  debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
  debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
  raise e
rescue => e
  debug "Error during #{method} request with Basic Auth: #{e.class.name}: #{e.message}", 1, :red
  debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
  raise e
end

def try_delete_latest_sessions

Try to delete sessions by direct URL when we can't list sessions
def try_delete_latest_sessions
  # Try to delete sessions by direct URL when we can't list sessions
  debug "Attempting to delete recent sessions directly...", 1
  base_url = determine_session_endpoint
  success = false
  
  # Try session IDs 1-10 (common for iDRAC)
  (1..10).each do |id|
    session_url = "#{base_url}/#{id}"
    begin
      delete_response = request_with_basic_auth(:delete, session_url, nil, 'application/json')
      
      if delete_response.status == 200 || delete_response.status == 204
        debug "Successfully deleted session: #{session_url}", 1, :green
        success = true
      else
        debug "Failed to delete session #{session_url}: #{delete_response.status}", 1, :red
      end
    rescue => e
      debug "Error deleting session #{session_url}: #{e.message}", 1, :red
    end
    
    # Small delay between deletions
    sleep(0.5)
  end
  
  return success
end