
require 'activerecord-import'

module Buildwise
	class App
		module BuildsHelper




			def find_build(id)
        if defined?(SERVER_DIGEST)
          @build = Build.where(:server_digest => SERVER_DIGEST, :id => id).first
        else 
          @build = Build.find_by_id(id)
        end
				@project = @build.project if @build
				return @build
			end

			def distributed_build_in_progress
				@test_files = TestFile.where(:build_id => @build.id).includes(:test_cases).order("priority DESC, filename ASC")
			end

			def distributed_build_complete
				@test_files ||= TestFile.where(:build_id => @build.id).includes(:test_cases).order("assigned_at ASC")
			end

			def quick_build_in_progress
				@ui_test_files = YAML.load(@build.acceptance_test_options)[:ui_test_file_list].split(",") rescue []
				@refresh_interval = 10
				logger.debug("Refresh UI Test report in progress: Build #{@build.id}: #{@build.stage}")

				if @build.is_ui_test_stage?
					ui_test_report_dir = @build.determine_ui_test_report_dir
					ui_test_dir = artifact_ui_test_dir = File.join(@build.artifacts_dir, "ui-tests")
					logger.debug("Parsing test results, folder => |#{ui_test_report_dir}| and test_dir => #{ui_test_dir}")
					if ui_test_report_dir

						ui_test_framework = @build.project.config.builder_ui_test_framework || @build.test_syntax_framework
						if ui_test_framework.nil? || ui_test_framework.empty?
							@build.test_syntax_framework = @build.determine_test_syntax_framework(ui_test_dir)        

						else
							@build.test_syntax_framework = ui_test_framework
						end


						begin
							case  @build.test_syntax_framework               
							when "Cucumber"
								@test_files = @build.parse_feature_results(ui_test_dir, ui_test_report_dir, :complete => false, :file_pattern => /^TEST.*\.xml$/i)
                  
							when "Mocha"
								@test_files = @build.parse_spec_results(ui_test_dir, ui_test_report_dir, :complete => false, :file_pattern => /^*\.xml$/i)


                @test_files.each do |tf|
                  if tf.new_record?
                    tf.filename = tf.filename.sub /\.[^\.]+$/, '.js'
                  end
                end
                
							when "unittest" # python       
								@test_files = @build.parse_spec_results(ui_test_dir, ui_test_report_dir, :complete => false, :file_pattern =>  /^*\.xml$/i)
                

                @test_files.each do |tf|
                  if tf.new_record?
                    tf.filename = tf.filename.sub /\.[^\.]+$/, '.py'
                  end
                end
                
							when "RSpec" # default to RSpec
								if File.exist?(File.join(ui_test_dir, "spec"))
									ui_test_dir = File.join(ui_test_dir, "spec")
								end

								@test_files = @build.parse_spec_results(ui_test_dir, ui_test_report_dir, :complete => false, :file_pattern => /^[SPEC|TEST].*\.xml$/i)
								
							else # unkown       

								@test_files = @build.parse_spec_results(ui_test_dir, ui_test_report_dir, :complete => false, :file_pattern =>  /^*\.xml$/i)
															
              end


						rescue TypeError => te
							puts "[ERROR] failed to parse tests: #{te}"

						end
					end
				end
			end

			def quick_build_complete
				@test_files = TestFile.where(:build_id => @build.id)
			end
      
      def is_server_test_execution_capable?(a_build)
        return false if a_build.nil? || a_build.class != Build || a_build.project.nil?
        @project = a_build.project
        

        if @build.is_parallel
          return false
        end
        
        is_rspec_project = @project.config.builder_ui_test_framework == "RSpec"
        is_mocha_project = @project.config.builder_ui_test_framework == "Mocha"
        is_pytest_project = @project.config.builder_ui_test_framework == "unittest" # python
        
        is_ui_test_project_dir_configured =  (@project.config.ui_tests_local_working_dir && Dir.exist?(@project.config.ui_tests_local_working_dir) )  ||  (@project.config.ui_tests_dir && Dir.exist?(@project.config.ui_tests_dir) ) 
        
        @capable_server_test_execution = (is_rspec_project || is_mocha_project || is_pytest_project) && is_ui_test_project_dir_configured
      end
			
      




      def detect_hanging_test_execution(a_build, max_time = 600)
        return if a_build.nil?
        if a_build.incomplete?
          agent_allocations = a_build.current_agent_allocations
          agent_allocations.sort!{|a,b| a.assigned_at <=> b.assigned_at }
          
          oldest_allocation = agent_allocations.first
          if (oldest_allocation)

            test_file_id = oldest_allocation.test_file_id
            @test_file = TestFile.find(test_file_id)
            execution_time = Time.now - oldest_allocation.assigned_at

            if @test_file && @test_file.build_id == a_build.id && execution_time > max_time
              logger.info("Detect a long running test execution on agent '#{oldest_allocation.agent_name}', #{execution_time}s (over #{max_time}), cancel it.")
              a_build.unassign_test(@test_file)                          
            end
          end
        end        
      end
      	
      

      def execute_rake_task_by_child_process(rake_command_arguments, envs = {})      
        require "childprocess"
         
        process = ChildProcess.build("rake", rake_command_arguments)
        process.detach = false  
        temp_file =  Tempfile.new("rake-output")     
        process.io.stdout = temp_file
        process.io.stderr = temp_file

        envs.each do |key, val|
          process.environment[key] = val                         
        end
                
        process.start        
        begin
          process.poll_for_exit(2 * 60) # 2 minutes
        rescue ChildProcess::TimeoutError
          process.stop # tries increasingly harsher methods to kill the process.
        end
        
        return File.read(temp_file)
      end
      
      def execute_rake_task(rake_command_arguments, work_dir = nil,  envs = {})   
        require 'open3'
        cmd = "rake #{rake_command_arguments}"
        saved_work_dir = File.expand_path('.')
        begin 
        FileUtils.chdir(work_dir) if work_dir
          puts("About to run command: #{cmd} in #{work_dir}")      
          stdout, stderr, status = Open3.capture3(cmd)          

          puts("OK")  
          return {:stdout => stdout, :stderr => stderr, :status => status}    
        ensure 
          FileUtils.chdir(saved_work_dir)
        end
      end
      


      def xxx_v1(the_build, ui_test_dir, test_syntax_framework = "rspec", options={})
        start_time = Time.now
        puts "#{Time.now}: start parsing: #{start_time}"


        
        test_file_names = []
        if Dir.exist?(ui_test_dir) 
          Dir.entries(ui_test_dir).each do |file_name|
            next if file_name == "." || file_name == ".."
        
            if the_build.test_syntax_framework == "mocha"
              next unless file_name =~ /\.js$/                        
            elsif the_build.test_syntax_framework =~ /cucumber/i 
              next unless file_name =~ /\.feature$/         
            elsif the_build.test_syntax_framework =~ /gauge/i 
              next unless file_name =~ /\.spec$/                   
            elsif the_build.test_syntax_framework =~ /unittest/i 
              next unless file_name =~ /\.py$/                                                
            elsif the_build.test_syntax_framework =~ /playwrighttest/i 
              next unless file_name =~ /\.ts$/                     
            else
              next unless file_name =~ /_spec\.rb$/ || file_name =~ /_test\.rb$/
            end
          
            next if options[:excluded] && options[:excluded].include?(file_name)
            next if options[:except] && options[:except].include?(file_name)
          
            test_file_names << file_name
          end
        end
        
        puts "{v1} test script files => #{test_file_names.count}"
        
        if options[:only] && options[:only].size > 0
          test_file_names.select!{|x| options[:only].include?(x) }
        end
        

        loop_times = 1 
        if @project &&  @project.config && @project.config.is_load_testing && @project.config.load_max_iteration_count > 1
          loop_times = @project.config.load_max_iteration_count.to_i
        end
        
        is_load_testing = @project &&  @project.config && @project.config.is_load_testing

        TestFile.transaction do    
          loop_times.times do 
            test_file_names.each do |file_name|
              test_file = TestFile.new(:filename => file_name, :created_at => Time.now)
              test_file.build_id = the_build.id if defined?(the_build)

              if is_load_testing 

                test_file.priority = rand(loop_times * test_file_names.size) + 1
              else              

                begin
                  test_file.determine_priority
                rescue => dpe            
                  logger.warn("[#{Time.now - start_time}] Error occurred on determine priority: #{dpe}, #{dpe.backtrace}")
                end
              end
              test_file.save 
            end
          end
        end

        puts "#{Time.now} v1: time cost: #{Time.now - start_time} s"        
      end #method


      def determine_test_files(the_build, ui_test_dir, specified_test_syntax_framework = "rspec", options={})
        start_time = Time.now
        logger.info("Determine test script files for test syntax framework: #{specified_test_syntax_framework}")
  
        puts "#{Time.now}: start parsing: #{start_time}"


      
        test_syntax_framework = specified_test_syntax_framework || the_build.test_syntax_framework
        test_syntax_framework = test_syntax_framework.downcase   
        test_file_names = []
        
        if test_syntax_framework == "junit"
          

          java_test_classes_string =  options[:test_classes]
          logger.info("[JUnit]: provided options: #{java_test_classes_string.class} | #{java_test_classes_string.to_s}")
          
          require 'json'
          java_test_classes = JSON.parse(java_test_classes_string)
          
          test_file_names = java_test_classes.collect{|x| x + ".class"}
          
        else 

          logger.info("[Scripting Language] Checking test script folder to find test script files...")


          if Dir.exist?(ui_test_dir) 
            Dir.entries(ui_test_dir).each do |file_name|
              next if file_name == "." || file_name == ".."
              the_file_name = file_name.downcase
            
              if test_syntax_framework == "mocha"
                next unless the_file_name.end_with?(".js")              
              elsif test_syntax_framework == "cucumber"
                next unless the_file_name.end_with?(".feature")         

              elsif test_syntax_framework == "unittest"
                next unless the_file_name.end_with?(".py")                                             

              elsif test_syntax_framework.include?("playwright")
                next unless the_file_name.end_with?(".ts")                       
              
              else
                next unless the_file_name.end_with?("_spec.rb") ||  the_file_name.end_with?("_test.rb") 
              end
        
              next if options[:excluded] && options[:excluded].include?(file_name)
              next if options[:except] && options[:except].include?(file_name)
        
              test_file_names << file_name
            end
          end        
        
        end
      
        the_project = the_build.project
        puts "{v2} test script files => #{test_file_names.count}"
              
        if options[:only] && options[:only].size > 0
          test_file_names.select!{|x| options[:only].include?(x) }
        end        
        
        loop_times = 1 
        if the_project &&  the_project.config && the_project.config.is_load_testing && the_project.config.load_max_iteration_count > 1
          loop_times = the_project.config.load_max_iteration_count.to_i
        end      
        is_load_testing = the_project &&  the_project.config && the_project.config.is_load_testing



=begin        
        if is_load_testing 
          TestFile.transaction do    
            loop_times.times do 
              test_file_names.each do |file_name|
                test_file = TestFile.new(:filename => file_name, :created_at => Time.now, :build_id => the_build.id,
                                         :priority => rand(loop_times * test_file_names.size) + 1)              
                test_file.save
              end
            end
          end   
                 
        else

          TestFile.transaction do              
            test_file_names.each {|file_name|
               TestFile.create(:filename => file_name, :created_at => Time.now, :build_id => the_build.id) 
            }
          end
        end
=end
                

        values = []
        loop_times.times do 
          test_file_names.each do |file_name|
            if is_load_testing 
             the_priority = rand(loop_times * test_file_names.size) + 1
            else

              the_last_run = TestFile.where(:filename => file_name).where("build_id < ?", the_build.id).where("priority IS NOT NULL").order("build_id").last
              if the_last_run
                the_priority = the_last_run.priority
              else 
                the_priority = 50 # treat as new, a high priority
              end
            end
            values << {filename: file_name, build_id: the_build.id, priority: the_priority }
          end
        end
        TestFile.import(values)
          


        puts "#{Time.now} v2: determine test files total : #{Time.now - start_time} s"                        
        
        return test_file_names # not used, only for unit testing      
      end
      

      def determine_test_files_priority(the_build, since_when = nil)
        test_files = TestFile.where(:build_id => the_build.id)
        if since_when
          test_files = test_files.where("created_at > ?", since_when)
        end
        
        sub_start_Time = Time.now
        puts "#{Time.now} v2: determin_priority for #{test_files.count} test files "                                        
        TestFile.transaction do                
          test_files.each do |test_file| 
            begin

              test_file.determine_priority
              test_file.save
            rescue => dpe            
              puts("[#{Time.now - sub_start_Time}] Error occurred on determine priority: #{dpe}, #{dpe.backtrace}")
              logger.warn("[#{Time.now - sub_start_Time}] Error occurred on determine priority: #{dpe}, #{dpe.backtrace}")
            end
          end            
        end
        puts "#{Time.now} v2: determin_priority : #{Time.now - sub_start_Time} "                                        
        logger.info "#{Time.now} v2: determin_priority : #{Time.now - sub_start_Time} "                                        
      end

      
      def determine_acceptance_test_context_to_file_lookups(the_build, ui_test_dir)
        sub_start_time = Time.now
        begin          
          the_build.acceptance_test_context_to_file_lookups = YAML.dump(the_build.build_context_to_file_lookups(ui_test_dir))
          the_build.save
        rescue => e
          logger.warn("Failed to parse lookups for ui_test_dir: #{ui_test_dir}: #{e}")
        end
        puts "#{Time.now} v2: acceptance_test_context_to_file_lookups cost : #{Time.now - sub_start_time} s"      
      end
      
		end # helper

		helpers BuildsHelper
	end
end