require 'rolify' # must include here, otherwise mi

class Project < ActiveRecord::Base
  has_many :build_queues
  has_many :builds

  has_many :distribution_rules

  has_many :user_stories

  validates :name, presence: true
  validates :identifier, presence: true
  validates :identifier, uniqueness: { scope: :server_digest, case_sensitive: true }

  include ::Rolify::Resource


  def self.retrieve_by_identifier(proj_identifier)
    if defined?(SERVER_DIGEST)
      Project.where(:identifier=> proj_identifier, :server_digest => SERVER_DIGEST)
    else
      Project.where(:identifier=> proj_identifier)
    end     
  end

  def self.retrieve_by_name(proj_name)
    if defined?(SERVER_DIGEST)
      Project.where(:identifier=> proj_name, :server_digest => SERVER_DIGEST)
    else
      Project.where(:identifier=> proj_name)
    end     
  end

  def self.retrieve_all
    if defined?(SERVER_DIGEST)
      Project.where(:server_digest => SERVER_DIGEST)
    else
      Project.all
    end     
  end
  
    
  def valid_builds(limit = nil)
    if limit.nil? || limit <= 0
      results = Build.where("project_id = ? AND (category IS NULL OR category != ?) AND status IS NOT NULL AND status != ?  AND status != ?", self.id, 'poll', 'unchanged', 'new').order("id desc")
    else

      results = Build.where("project_id = ? AND (category IS NULL OR category != ?) AND status IS NOT NULL AND status != ?  AND status != ?", self.id, 'poll', 'unchanged', 'new').order("id desc").limit(limit)
    end
    return results
  end

  def self.load_projects_from_working_dir(project_identifiers = [], project_names = [])
    puts("[INIT] Loading projects from working dir: #{BUILDWISE_HOME}")
    
    work_dir = File.join(BUILDWISE_HOME, "work")
    FileUtils.mkdir_p(work_dir) unless File.exist?(work_dir)
    
    config_dir = File.join(BUILDWISE_HOME, "config")
    FileUtils.mkdir_p(config_dir) unless File.exist?(config_dir)

    project_config_files = Dir["#{BUILDWISE_HOME}/config/*.xml"].collect{|x| File.basename(x, ".xml") };
    if project_identifiers.empty?
      project_identifiers = project_config_files
    end  

    (project_identifiers & project_config_files).each do |project_identifier|
        project_work_dir = File.join(work_dir, project_identifier)
        FileUtils.mkdir_p(project_work_dir) unless File.exist?(project_work_dir)      
        FileUtils.rm_f(File.join(project_work_dir, ".lock")) if File.exist?(File.join(project_work_dir, ".lock"))

        the_proj = Project.retrieve_by_identifier(project_identifier).first
        if the_proj.nil?
          
          if project_names.size == project_identifiers.size
            project_name = project_names[project_identifiers.index(project_identifier)]
            puts "Creating new project: #{project_identifier} | #{project_name}"
            the_proj = Project.new(:identifier => project_identifier, :name => project_name)
          else
            the_proj = Project.new(:identifier => project_identifier, :name => project_identifier)
          end
          the_proj.server_digest = SERVER_DIGEST if defined?(SERVER_DIGEST)          
          the_proj.working_dir = File.join(BUILDWISE_HOME, "work", project_identifier)
          the_proj.save
        end        
    end
    
  end

  def build_progress_percentage
    begin
      the_last_build_duration = last_complete_build.duration
    rescue => e

      default_build_time = 600 # 10 minutes when there is no builds, set default time to 10 mins
      the_last_build_duration = default_build_time
    end


    begin
      last_build.elapsed_time_in_progress * 100 / the_last_build_duration
    rescue => e

      0
    end
  end


  def has_pending_poll_builds?
    found = build_queues.any? { |x| x.status == "pending" && x.category == "poll" }
  end

  def previous_build(current_build)
    all_builds = valid_builds(25).reverse # early first
    index = get_build_index(all_builds, current_build.label)

    if index > 0
      return all_builds[index-1]
    else
      return nil
    end
  end

  def next_build(current_build)
    all_builds = valid_builds(25).reverse # early first
    index = get_build_index(all_builds, current_build.label)

    if index == (all_builds.count - 1)
      return nil
    else
      return all_builds[index + 1]
    end
  end

  def get_build_index(all_builds, build_label)
    result = 0;
    all_builds.each_with_index { |build, index| result = index if build.label == build_label }
    result
  end

  def config_file_path
    buildwise_root_dir = nil
    if self.working_dir && File.exist?(self.working_dir)
      buildwise_root_dir = File.expand_path( File.join(self.working_dir, "..", ".."))
      if File.exist?( File.join(buildwise_root_dir, "config", "#{identifier}.xml") )
        return File.join(buildwise_root_dir, "config", "#{identifier}.xml")
      end
    end
    
    File.join(BUILDWISE_HOME, "config", "#{identifier}.xml")
  end

  def config_file_content
    File.read(config_file_path)
  end

  def update_config(content)
    IO.write(config_file_path, content)
  end

  def last_complete_build_status
    the_last_complete_build = last_complete_build
    if the_last_complete_build
      the_last_complete_build.successful ? "success" : "failed"
    else
      'never_built'
    end
  end

  def is_building?
    begin
      last_build.status == "building"
    rescue => e
      return false
    end
  end

  def last_complete_build
    Build.where("project_id = ? AND status = ?", self.id, 'complete').order(:id).last
  end

  def last_build
    Build.where("project_id = ?", self.id).order(:id).last
  end

  def last_build_id
    last_build.id rescue nil
  end

  def last_five_builds
    Build.where("project_id = ? AND status != ? AND status != ?", self.id, "new", 'unchanged').order("ID desc").limit(5)
  end


  def last_builds(number, max_id = nil)
    if max_id.nil?
      array = Build.where("project_id = ? AND status != ? AND status != ? AND is_invalid != ?", self.id, "new", 'unchanged', true).order("ID desc").limit(number).to_a
    else
      array = Build.where("project_id = ? AND status != ? AND status != ? AND is_invalid != ? AND id <= ?", self.id, "new", 'unchanged', true, max_id).order("ID desc").limit(number).to_a 
    end
    return array
  end

  def last_two_builds
    Build.where("project_id = ? AND status != ? AND status != ?", self.id, "new", 'unchanged').order("ID desc").limit(2)
  end

  def config(refresh = true)
    if refresh || @_config.nil?

      @_config = ::BuildWise::ProjectConfiguration.get(identifier)
    else
      @_config
    end
    if @_config.nil? # might upgrade
      if self.configuration && self.configuration.include?("<configuration")

        self.update_config(self.configuration)
        @_config = ::BuildWise::ProjectConfiguration.get(identifier)        
      else
        config_file = File.join(BUILDWISE_HOME, "config", "#{identifier}.xml")
        @_config = ::BuildWise::ProjectConfiguration.new(config_file)
        @_config.application_name = self.name
        @_config.save
      end
    end
    return @_config
  end

  def source_control
    config.scm_type
  end

  def builder_error_message
    ""
  end

  def generate_label(revision)
    if revision
      same_rev_count = builds.count { |x| x.revision == revision }
      return revision.to_s.slice(0, 8) if same_rev_count < 1
      return "#{revision.to_s.slice(0, 8)}.#{same_rev_count}"
    else
      return nil
    end
  end

  def checkout_dir
    File.join(working_dir, "sources")
  end

  def ui_test_dir
    if config.ui_tests_dir
      File.join(checkout_dir, config.ui_tests_dir)
    elsif File.exist?(File.join(checkout_dir, "test", "acceptance")) then
      File.join(checkout_dir, "test", "acceptance")
    elsif File.exist?(File.join(checkout_dir, "rspec")) then
      File.join(checkout_dir, "rspec")
    else
      File.join(checkout_dir)
    end
  end

  def ui_test_report_dir
    if config.ui_tests_report_dir
      File.join(checkout_dir, config.ui_tests_report_dir)
    else
      return nil
    end
  end

  def ui_test_local_working_dir
    config.ui_tests_local_working_dir
  end




  def ui_test_priorities

    last_five_complete_builds = Build.where("project_id = ? AND status = ?", self.id, 'complete').order("id desc").first(5)

    outcome_hash = {}

    last_five_complete_builds.each do |a_build|
      a_build.test_files.each do |test_file|
        next if test_file.nil? || test_file.filename.nil?
        filename= File.basename(test_file.filename)

        outcome_hash[filename] ||= 5
        if test_file.failed? || test_file.num_total == 0
        elsif outcome_hash[filename] -= 1
        end
      end
    end


    test_order = outcome_hash.sort_by { |k, v| v }.reverse.collect { |k, v| k }

    return test_order
  end


  def get_build_tests_by_status(test_pass = true)
    last_completed_build = Build.where("project_id = ? AND status = ?", self.id, "complete").order(:id).last
    if last_completed_build && last_completed_build.artifacts_dir then
      ui_tests_dir = File.join(last_completed_build.artifacts_dir, "ui-tests")
      matching_test_files = last_completed_build.test_files.collect { |x|
        the_test_file_name = x.filename
        if test_pass
          next if x.failed?
        else
          next unless x.failed?
        end

        if ui_tests_dir.include?(ui_tests_dir)
          the_test_file_name.sub!(ui_tests_dir + "/", "")
        end
        the_test_file_name
      }
      matching_test_files.compact.join(",")
    else
      return nil
    end
  end

  

  def build_steps()
    begin 
      self.config.build_steps.to_a.select{|x| x.enabled }.sort{|a, b| a.order.to_i <=> b.order.to_i }
    rescue => e
      puts("[WARN] failed get build steps by order: #{e}")
      self.config.build_steps
    end
  end

  def is_distributed?
    return (config.app_name && config.app_name.size > 1) && (config.agent_work_dir && config.agent_work_dir.size > 1)
  end


  def available_agents    
    the_agents = TestFile.joins(:build).where("test_files.created_at > ?", 1.month.ago).where(builds: { project_id: self.id }).order("test_files.id DESC").limit(5000).pluck("agent").compact.uniq
  end


  def recent_build_test_files
    recent_builds = self.builds.last(10).to_a
    build_test_file_names = []
    recent_builds.each do |x|
      build_test_file_names << x.test_files.collect { |x| x.filename  }
    end
    build_test_file_names.flatten.uniq
  end
  

  def active_distribution_rules
    DistributionRule.where(:project_id => self.id, :active => true)
  end

  def total_ui_test_count
    TestFile.joins(:build).where(:builds => {:project_id => self.id}).count    
  end
  
  def roles
    Role.where(:resource_type => "Project", :resource_id => self.id)
  end    

  




  def support_traceability?
    self.config.builder_ui_test_framework == "RSpec"
  end
  
  def generate_traceability_matrix(opts = { })
    test_files = []
    spec_files = []
    test_lookups = {}

    test_dir = File.join(self.working_dir, "sources", self.config.ui_tests_dir)
    return  if test_dir.nil? || !File.exist?(test_dir)

    require 'find'
    Find.find(test_dir) do |f|

			unless opts[:scan_sub_dir]
				if File.directory?(f) && File.expand_path(f) != File.expand_path(test_dir)
					Find.prune
				end
			end

      if f =~ /(_spec|_test)\.rb$/
        test_files << f
        spec_file = SpecFile.new(f)
				if opts[:local_work_dir]
					relative_path = File.expand_path(f).gsub(File.expand_path(test_dir), "")
					spec_file.local_work_path = File.join(opts[:local_work_dir], relative_path)
				end
        spec_file.spec_contexts.each { |ctx|
          ctx.spec_behaviours.each { |test_case|
            next if test_case.nil? or test_case.name.nil? or test_case.name.empty?

            if test_case.name =~ /^\s*['|"]?\[(.*)\]/
              puts $1
              test_case_id_str = $1
              test_case_ids = test_case_id_str.split(",").collect {|x| x.strip }

              test_case_ids.each do |tid|
                if test_lookups[tid].nil? then
                  test_lookups[tid] = []
                end
                test_lookups[tid] << test_case
              end
            end
          }
        }
         spec_files << spec_file
      end
    end


    return { :test_files => test_files, :spec_files => spec_files, :requirement_tests => test_lookups}
  end
 
 
 def recent_load_operations
   recent_load_build_ids = Build.where(:project_id => self.id).where(:is_load_testing => true).order("ID DESC").limit(10).pluck("id")
   operations = LoadTestResult.where("build_id IN (?)", recent_load_build_ids).pluck("operation").uniq.sort
   return operations;
 end
 

end