class IDRAC::Firmware

def check_updates(catalog_path = nil)

def check_updates(catalog_path = nil)
  # Download catalog if not provided
  catalog_path ||= download_catalog
  
  # Get system inventory
  inventory = get_system_inventory
  
  # Parse catalog
  catalog_doc = File.open(catalog_path) { |f| Nokogiri::XML(f) }
  
  # Extract service tag
  service_tag = inventory[:system][:service_tag]
  
  puts "Checking updates for system with service tag: #{service_tag}"
  
  # Find applicable updates
  updates = []
  
  # Get current firmware versions
  current_versions = {}
  inventory[:firmware].each do |fw|
    current_versions[fw[:name]] = fw[:version]
  end
  
  # Find matching components in catalog
  catalog_doc.xpath('//SoftwareComponent').each do |component|
    name = component.at_xpath('Name')&.text
    version = component.at_xpath('Version')&.text
    path = component.at_xpath('Path')&.text
    component_type = component.at_xpath('ComponentType')&.text
    
    # Check if this component matches any of our firmware
    inventory[:firmware].each do |fw|
      if fw[:name].include?(name) || name.include?(fw[:name])
        current_version = fw[:version]
        
        # Simple version comparison (this could be improved)
        if version != current_version
          updates << {
            name: name,
            current_version: current_version,
            available_version: version,
            path: path,
            component_type: component_type,
            download_url: "https://downloads.dell.com/#{path}"
          }
        end
      end
    end
  end
  
  updates
end

def download_catalog(output_dir = nil)

def download_catalog(output_dir = nil)
  # Use ~/.idrac as the default directory
  output_dir ||= File.expand_path("~/.idrac")
  FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
  
  catalog_gz_path = File.join(output_dir, "Catalog.xml.gz")
  catalog_path = File.join(output_dir, "Catalog.xml")
  
  puts "Downloading Dell catalog from #{CATALOG_URL}..."
  
  uri = URI.parse(CATALOG_URL)
  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    request = Net::HTTP::Get.new(uri)
    http.request(request) do |response|
      if response.code == "200"
        File.open(catalog_gz_path, 'wb') do |file|
          response.read_body do |chunk|
            file.write(chunk)
          end
        end
      else
        raise Error, "Failed to download catalog: #{response.code} #{response.message}"
      end
    end
  end
  
  puts "Extracting catalog..."
  system("gunzip -f #{catalog_gz_path}")
  
  if File.exist?(catalog_path)
    puts "Catalog downloaded and extracted to #{catalog_path}"
    return catalog_path
  else
    raise Error, "Failed to extract catalog"
  end
end

def get_job_status(job_id)

def get_job_status(job_id)
  response = client.authenticated_request(
    :get,
    "/redfish/v1/TaskService/Tasks/#{job_id}"
  )
  
  if response.status != 200
    raise Error, "Failed to get job status with status #{response.status}: #{response.body}"
  end
  
  response_data = JSON.parse(response.body)
  response_data['TaskState'] || 'Unknown'
end

def get_system_inventory

def get_system_inventory
  puts "Retrieving system inventory..."
  
  # Get basic system information
  system_uri = URI.parse("#{client.base_url}/redfish/v1/Systems/System.Embedded.1")
  system_response = client.authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
  
  if system_response.status != 200
    raise Error, "Failed to get system information: #{system_response.status}"
  end
  
  system_data = JSON.parse(system_response.body)
  
  # Get firmware inventory
  firmware_uri = URI.parse("#{client.base_url}/redfish/v1/UpdateService/FirmwareInventory")
  firmware_response = client.authenticated_request(:get, "/redfish/v1/UpdateService/FirmwareInventory")
  
  if firmware_response.status != 200
    raise Error, "Failed to get firmware inventory: #{firmware_response.status}"
  end
  
  firmware_data = JSON.parse(firmware_response.body)
  
  # Get detailed firmware information for each component
  firmware_inventory = []
  
  if firmware_data['Members'] && firmware_data['Members'].is_a?(Array)
    firmware_data['Members'].each do |member|
      if member['@odata.id']
        component_uri = member['@odata.id']
        component_response = client.authenticated_request(:get, component_uri)
        
        if component_response.status == 200
          component_data = JSON.parse(component_response.body)
          firmware_inventory << {
            name: component_data['Name'],
            id: component_data['Id'],
            version: component_data['Version'],
            updateable: component_data['Updateable'] || false,
            status: component_data['Status'] ? component_data['Status']['State'] : 'Unknown'
          }
        end
      end
    end
  end
  
  {
    system: {
      model: system_data['Model'],
      manufacturer: system_data['Manufacturer'],
      serial_number: system_data['SerialNumber'],
      part_number: system_data['PartNumber'],
      bios_version: system_data['BiosVersion'],
      service_tag: system_data['SKU']
    },
    firmware: firmware_inventory
  }
end

def initialize(client)

def initialize(client)
  @client = client
end

def interactive_update(catalog_path = nil)

def interactive_update(catalog_path = nil)
  updates = check_updates(catalog_path)
  
  if updates.empty?
    puts "No updates available for your system."
    return
  end
  
  puts "\nAvailable updates:"
  updates.each_with_index do |update, index|
    puts "#{index + 1}. #{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}"
  end
  
  puts "\nEnter the number of the update to install (or 'all' for all updates, 'q' to quit):"
  choice = STDIN.gets.chomp
  
  return if choice.downcase == 'q'
  
  selected_updates = if choice.downcase == 'all'
                      updates
                    else
                      index = choice.to_i - 1
                      if index >= 0 && index < updates.size
                        [updates[index]]
                      else
                        puts "Invalid selection."
                        return
                      end
                    end
  
  selected_updates.each do |update|
    puts "Downloading #{update[:name]} version #{update[:available_version]}..."
    
    # Create temp directory
    temp_dir = Dir.mktmpdir
    
    begin
      # Download the update
      update_filename = File.basename(update[:path])
      update_path = File.join(temp_dir, update_filename)
      
      uri = URI.parse(update[:download_url])
      Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
        request = Net::HTTP::Get.new(uri)
        http.request(request) do |response|
          if response.code == "200"
            File.open(update_path, 'wb') do |file|
              response.read_body do |chunk|
                file.write(chunk)
              end
            end
          else
            puts "Failed to download update: #{response.code} #{response.message}"
            next
          end
        end
      end
      
      puts "Installing #{update[:name]} version #{update[:available_version]}..."
      job_id = update(update_path, wait: true)
      puts "Update completed with job ID: #{job_id}"
      
    ensure
      # Clean up temp directory
      FileUtils.remove_entry(temp_dir)
    end
  end
end

def update(firmware_path, options = {})

def update(firmware_path, options = {})
  # Validate firmware file exists
  unless File.exist?(firmware_path)
    raise Error, "Firmware file not found: #{firmware_path}"
  end
  # Login to iDRAC
  client.login unless client.instance_variable_get(:@session_id)
  # Upload firmware file
  job_id = upload_firmware(firmware_path)
  
  # Check if we should wait for the update to complete
  if options[:wait]
    wait_for_job_completion(job_id, options[:timeout] || 3600)
  end
  job_id
end

def upload_firmware(firmware_path)

def upload_firmware(firmware_path)
  puts "Uploading firmware file: #{firmware_path}"
  
  # Get the HttpPushUri from UpdateService
  update_service_response = client.authenticated_request(:get, "/redfish/v1/UpdateService")
  update_service_data = JSON.parse(update_service_response.body)
  
  http_push_uri = update_service_data['HttpPushUri']
  if http_push_uri.nil?
    http_push_uri = "/redfish/v1/UpdateService/FirmwareInventory"
    puts "HttpPushUri not found, using default: #{http_push_uri}"
  else
    puts "Found HttpPushUri: #{http_push_uri}"
  end
  
  # Get the ETag for the HttpPushUri
  etag_response = client.authenticated_request(:get, http_push_uri)
  etag = etag_response.headers['etag']
  
  puts "Got ETag: #{etag}"
  
  # Create a boundary for multipart/form-data
  boundary = "----WebKitFormBoundary#{SecureRandom.hex(16)}"
  
  # Read the file content
  file_content = File.binread(firmware_path)
  filename = File.basename(firmware_path)
  
  # Create the multipart body
  post_body = []
  post_body << "--#{boundary}\r\n"
  post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
  post_body << "Content-Type: application/octet-stream\r\n\r\n"
  post_body << file_content
  post_body << "\r\n--#{boundary}--\r\n"
  
  # Upload the firmware
  response = client.authenticated_request(
    :post,
    http_push_uri,
    {
      headers: {
        'Content-Type' => "multipart/form-data; boundary=#{boundary}",
        'If-Match' => etag
      },
      body: post_body.join
    }
  )
  
  if response.status < 200 || response.status >= 300
    raise Error, "Firmware upload failed with status #{response.status}: #{response.body}"
  end
  
  # Extract job ID from response
  response_data = JSON.parse(response.body)
  job_id = response_data['Id'] || response_data['TaskId']
  
  if job_id.nil?
    raise Error, "Failed to extract job ID from firmware upload response"
  end
  
  puts "Firmware update job created with ID: #{job_id}"
  job_id
end

def wait_for_job_completion(job_id, timeout)

def wait_for_job_completion(job_id, timeout)
  puts "Waiting for firmware update job #{job_id} to complete..."
  
  start_time = Time.now
  loop do
    status = get_job_status(job_id)
    
    case status
    when 'Completed'
      puts "Firmware update completed successfully"
      return true
    when 'Failed'
      raise Error, "Firmware update job failed"
    when 'Scheduled', 'Running', 'Downloading', 'Pending'
      # Job still in progress
    else
      puts "Unknown job status: #{status}"
    end
    
    if Time.now - start_time > timeout
      raise Error, "Firmware update timed out after #{timeout} seconds"
    end
    
    # Wait before checking again
    sleep 10
  end
end