lib/idrac/utility.rb



module IDRAC
  module Utility
    include Debuggable

    # Generate TSR (Technical Support Report) logs using SupportAssistCollection for local generation
    # @param data_selector_values [Array] Array of log types to include (optional)
    #   Default includes all available log types
    # @return [Hash] Result hash with status and job/task information
    def generate_tsr_logs(data_selector_values: nil, share_type: nil, share_parameters: nil)
      debug "Generating TSR/SupportAssist logs...", 1
      
      # Check EULA status first
      eula_status = supportassist_eula_status
      if eula_status["EULAAccepted"] == false || eula_status["EULAAccepted"] == "false"
        puts "\n" + "="*80
        puts "ERROR: SupportAssist EULA Not Accepted".red.bold
        puts "="*80
        puts ""
        puts "The SupportAssist End User License Agreement (EULA) must be accepted".yellow
        puts "before you can generate TSR/SupportAssist collections.".yellow
        puts ""
        puts "To accept the EULA, run:".cyan
        puts "  idrac tsr_accept_eula --host #{@host} --port #{@port}".green.bold
        puts ""
        puts "="*80
        return { status: :failed, error: "SupportAssist EULA not accepted" }
      end
      
      # Default data selector values for comprehensive TSR
      # Valid values for SupportAssistCollection: "DebugLogs", "GPULogs", "HWData", "OSAppData", "TTYLogs", "TelemetryReports"
      data_selector_values ||= ["HWData", "OSAppData"]  
      
      # Map numeric values to iDRAC expected strings if needed
      if data_selector_values.is_a?(Array) && data_selector_values.first.to_s =~ /^\d+$/
        data_selector_values = data_selector_values.map do |val|
          case val.to_s
          when "0" then "HWData"
          when "1" then "OSAppData"  
          when "2" then "TTYLogs"
          when "3" then "DebugLogs"
          else "HWData"  # Default to HWData
          end
        end
      elsif data_selector_values.is_a?(String)
        data_selector_values = data_selector_values.split(',')
      end
      
      debug "Data selector values: #{data_selector_values.inspect}", 1
      
      # Use SupportAssistCollection for local generation as it supports "Local" ShareType
      payload = {
        "ShareType" => "Local",
        "DataSelectorArrayIn" => data_selector_values,
        "Filter" => "No",  # Don't filter PII
        "Transmit" => "No"  # Don't transmit to Dell
      }
      
      debug "SupportAssist collection payload: #{payload.to_json}", 1
      
      response = authenticated_request(
        :post,
        "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.SupportAssistCollection",
        body: payload.to_json,
        headers: { 'Content-Type' => 'application/json' }
      )
      
      case response.status
      when 202
        # Accepted - job created
        location = response.headers["location"]
        if location
          debug "TSR generation job created: #{location}", 1, :green
          job_id = location.split("/").last
          # Wait for job to complete and capture the file location
          job_result = wait_for_job_with_location(job_id)
          if job_result && (job_result["JobState"] == "Completed" || job_result["JobState"] == "CompletedWithErrors")
            result = { status: :success, job: job_result }
            # Check if we got a file location from the job completion
            result[:location] = job_result["FileLocation"] if job_result["FileLocation"]
            result
          else
            { status: :failed, error: "Job did not complete successfully" }
          end
        else
          { status: :accepted, message: "TSR generation initiated" }
        end
      when 200..299
        debug "TSR generation completed immediately", 1, :green
        { status: :success }
      else
        error_msg = parse_error_response(response)
        debug "Failed to generate TSR: #{error_msg}", 1, :red
        { status: :failed, error: error_msg }
      end
    rescue => e
      debug "Error generating TSR: #{e.message}", 1, :red
      { status: :error, error: e.message }
    end

    # Download TSR/SupportAssist logs from a URL location
    # @param location [String] URL location of the TSR file
    # @param output_file [String] Path to save the TSR file (optional)
    # @return [String, nil] Path to downloaded file or nil if failed
    def download_tsr_from_location(location, output_file: nil)
      debug "Downloading TSR from location: #{location}", 1
      
      # Default output filename with timestamp
      output_file ||= "supportassist_#{@host}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
      
      # Download the file from the location
      file_response = authenticated_request(:get, location)
      
      if file_response.status == 200 && file_response.body
        File.open(output_file, 'wb') do |f|
          f.write(file_response.body)
        end
        debug "TSR saved to: #{output_file} (#{File.size(output_file)} bytes)", 1, :green
        return output_file
      else
        debug "Failed to download file from location. Status: #{file_response.status}", 1, :red
        nil
      end
    rescue => e
      debug "Error downloading TSR: #{e.message}", 1, :red
      nil
    end
    
    # Wait for job and capture file location from response headers
    # @param job_id [String] The job ID to wait for
    # @param max_wait [Integer] Maximum time to wait in seconds
    # @return [Hash, nil] Job data with FileLocation if available
    def wait_for_job_with_location(job_id, max_wait: 600)
      debug "Waiting for job #{job_id} to complete...", 1
      start_time = Time.now
      
      while (Time.now - start_time) < max_wait
        job_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/#{job_id}")
        
        if job_response.status == 200
          job_data = JSON.parse(job_response.body)
          
          case job_data["JobState"]
          when "Completed", "CompletedWithErrors"
            debug "Job #{job_id} completed: #{job_data["JobState"]}", 1, :green
            
            # Check response headers for file location
            if job_response.headers["location"]
              job_data["FileLocation"] = job_response.headers["location"]
              debug "Found file location in headers: #{job_data["FileLocation"]}", 1, :green
            end
            
            # Also check the job data itself for output location
            if job_data["Oem"] && job_data["Oem"]["Dell"] && job_data["Oem"]["Dell"]["OutputLocation"]
              job_data["FileLocation"] = job_data["Oem"]["Dell"]["OutputLocation"]
              debug "Found file location in job data: #{job_data["FileLocation"]}", 1, :green
            end
            
            return job_data
          when "Failed", "Exception"
            debug "Job #{job_id} failed: #{job_data["Message"]}", 1, :red
            return job_data
          else
            debug "Job #{job_id} state: #{job_data["JobState"]} - #{job_data["PercentComplete"]}%", 2
            sleep 5
          end
        else
          debug "Failed to get job status: #{job_response.status}", 2
          sleep 5
        end
      end
      
      debug "Timeout waiting for job #{job_id}", 1, :red
      nil
    end
    
    # Parse error response from iDRAC
    def parse_error_response(response)
      begin
        data = JSON.parse(response.body)
        if data["error"] && data["error"]["@Message.ExtendedInfo"]
          data["error"]["@Message.ExtendedInfo"].first["Message"]
        elsif data["error"] && data["error"]["message"]
          data["error"]["message"]
        else
          "Status: #{response.status} - #{response.body}"
        end
      rescue
        "Status: #{response.status} - #{response.body}"
      end
    end

    # Generate and download TSR logs in a single operation
    # @param output_file [String] Path to save the TSR file (optional)
    # @param data_selector_values [Array] Array of log types to include (optional)
    # @param wait_timeout [Integer] Maximum time to wait for generation in seconds (default: 600)
    # @return [String, nil] Path to downloaded file or nil if failed
    def generate_and_download_tsr(output_file: nil, data_selector_values: nil, wait_timeout: 600)
      debug "Starting TSR generation and download process...", 1
      
      output_file ||= "supportassist_#{@host}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
      
      # First, generate the TSR
      result = generate_tsr_logs(data_selector_values: data_selector_values)
      
      if result[:status] == :success && result[:job]
        debug "TSR generation completed successfully", 1, :green
        
        # Check if the job response has a location for the file
        if result[:location]
          return download_tsr_from_location(result[:location], output_file: output_file)
        else
          # Try alternative download methods based on Dell's Python script approach
          debug "Attempting to locate generated TSR file...", 1, :yellow
          
          # Wait a moment for the file to be available
          sleep 2
          
          # Try known endpoints where the file might be available
          possible_locations = [
            "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/ExportedFiles/SupportAssist",
            "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLCService/ExportedFiles",
            "/downloads/supportassist_collection.zip",
            "/sysmgmt/2016/server/support_assist_collection"
          ]
          
          possible_locations.each do |location|
            debug "Trying location: #{location}", 2
            file_response = authenticated_request(:get, location)
            
            if file_response.status == 200 && file_response.body && file_response.body.size > 1024
              File.open(output_file, 'wb') do |f|
                f.write(file_response.body)
              end
              debug "TSR saved to: #{output_file} (#{File.size(output_file)} bytes)", 1, :green
              return output_file
            end
          end
          
          debug "Could not locate TSR file for direct download", 1, :yellow
          debug "The collection was generated but may require network share export", 1, :yellow
        end
      elsif result[:status] == :accepted
        debug "TSR generation was accepted but status unknown", 1, :yellow
      else
        debug "Failed to initiate TSR generation: #{result[:error]}", 1, :red
      end
      
      nil
    end
    public

    # Get TSR/SupportAssist collection status
    # @return [Hash] Status information
    def tsr_status
      debug "Checking SupportAssist collection status...", 1
      
      response = authenticated_request(
        :get,
        "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService"
      )
      
      if response.status == 200
        data = JSON.parse(response.body)
        status = {
          available: data["Actions"]&.key?("#DellLCService.SupportAssistCollection"),
          export_available: data["Actions"]&.key?("#DellLCService.SupportAssistExportLastCollection"),
          collection_in_progress: false
        }
        
        # Check if there's an active collection job
        jobs_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs")
        if jobs_response.status == 200
          jobs_data = JSON.parse(jobs_response.body)
          if jobs_data["Members"]
            jobs_data["Members"].each do |job|
              if job["Name"]&.include?("SupportAssist") || job["Name"]&.include?("TSR")
                status[:collection_in_progress] = true
                status[:job_id] = job["Id"]
                status[:job_state] = job["JobState"]
                break
              end
            end
          end
        end
        
        debug "SupportAssist status: #{status.to_json}", 2
        status
      else
        debug "Failed to get SupportAssist status: #{response.status}", 1, :red
        { available: false, error: "Unable to determine status" }
      end
    rescue => e
      debug "Error checking SupportAssist status: #{e.message}", 1, :red
      { available: false, error: e.message }
    end

    # Check SupportAssist EULA status
    # @return [Hash] EULA status information
    def supportassist_eula_status
      debug "Checking SupportAssist EULA status...", 1
      
      response = authenticated_request(
        :post,
        "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.SupportAssistGetEULAStatus",
        body: {}.to_json,
        headers: { 'Content-Type' => 'application/json' }
      )
      
      if response.status.between?(200, 299)
        begin
          data = JSON.parse(response.body)
          debug "EULA status: #{data.to_json}", 2
          return data
        rescue JSON::ParserError
          return { "EULAAccepted" => "Unknown" }
        end
      else
        error_msg = parse_error_response(response)
        debug "Failed to get EULA status: #{error_msg}", 1, :red
        return { "EULAAccepted" => "Error", "error" => error_msg }
      end
    rescue => e
      debug "Error checking EULA status: #{e.message}", 1, :red
      { "EULAAccepted" => "Error", "error" => e.message }
    end
    
    # Accept SupportAssist EULA
    # @return [Boolean] true if successful
    def accept_supportassist_eula
      debug "Accepting SupportAssist EULA...", 1
      
      response = authenticated_request(
        :post,
        "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.SupportAssistAcceptEULA",
        body: {}.to_json,
        headers: { 'Content-Type' => 'application/json' }
      )
      
      if response.status.between?(200, 299)
        debug "SupportAssist EULA accepted successfully", 1, :green
        true
      else
        error_msg = parse_error_response(response)
        debug "Failed to accept EULA: #{error_msg}", 1, :red
        false
      end
    rescue => e
      debug "Error accepting EULA: #{e.message}", 1, :red
      false
    end
    
    # Reset the iDRAC controller (graceful restart)
    def reset!
      debug "Resetting iDRAC controller...", 1
      
      response = authenticated_request(
        :post,
        "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Manager.Reset",
        body: { "ResetType" => "GracefulRestart" }.to_json,
        headers: { 'Content-Type' => 'application/json' }
      )
      
      if response.status.between?(200, 299)
        debug "Reset command accepted, waiting for iDRAC to restart...", 1, :green
        tries = 0
        
        while true
          begin
            debug "Checking if iDRAC is back online...", 1
            response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1")
            if response.status.between?(200, 299)
              debug "iDRAC is back online!", 1, :green
              break
            end
            sleep 30
          rescue => e
            tries += 1
            if tries > 5
              debug "Failed to reconnect to iDRAC after 5 attempts", 1, :red
              return false
            end
            debug "No response from server... retry #{tries}/5", 1, :red
            sleep 2 ** tries
          end
        end
      else
        begin
          error_data = JSON.parse(response.body)
          if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
            message = error_data["error"]["@Message.ExtendedInfo"].first["Message"]
            debug "*" * 80, 1, :red
            debug message, 1, :red
            debug "*" * 80, 1, :red
          else
            debug "Failed to reset iDRAC. Status code: #{response.status}", 1, :red
          end
        rescue => e
          debug "Failed to reset iDRAC. Status code: #{response.status}", 1, :red
          debug "Error response: #{response.body}", 2, :red
        end
        return false
      end
      
      true
    end
  end
end