require 'active_record'

require 'debci'
require 'debci/package_status'

module Debci
  # This class represents a single package.

  class Package < ::ActiveRecord::Base
    has_many :jobs, class_name: 'Debci::Job'
    has_many :package_status, class_name: 'Debci::PackageStatus'
    validates_format_of :name, with: /\A[a-z0-9][a-z0-9+.-]+\z/
    validates :backend, inclusion: { in: ->(_) { Debci.config.backend_list } }, allow_nil: true
    validates :storage_limit, inclusion: { in: (1..), message: "is not greater than zero" }, allow_nil: true
    validates :name, uniqueness: true

    scope :by_prefix, lambda { |p|
      if p == 'l'
        where("name LIKE :prefix AND name NOT LIKE 'lib%'", prefix: "#{p}%")
      else
        where("name LIKE :prefix", prefix: "#{p}%")
      end
    }

    # Returns a matrix of Debci::Job objects, where rows represent
    # architectures and columns represent suites:
    #
    #     [
    #       [ amd64_unstable , amd64_testing ],
    #       [ i386_unstable, i386_testing ],
    #     ]
    #
    # Each cell of the matrix contains a Debci::Job object.
    # Note: Contains statuses which are not rejectlisted
    def status
      @status ||=
        begin
          map = package_status.includes(:job).each_with_object({}) do |st, memo|
            memo[st.arch] ||= {}
            memo[st.arch][st.suite] = st.job
          end
          Debci.config.arch_list.map do |arch|
            Debci.config.suite_list.map do |suite|
              map[arch] && map[arch][suite]
            end
          end
        end
    end

    # Returns an array of Debci::Job objects that represent the test
    # history for this package
    def history(suite, architecture)
      Debci::Job.history(self, suite, architecture)
    end

    def news
      limit = 10
      result = []
      current = {}
      candidates = jobs.where.not(status: [nil, 'tmpfail']).where(pin_packages: nil)
      candidates = candidates.order('date DESC')

      # This manually written batch iteration can be replaced by
      # ActiveRecord::Base.find_in_batches(cursor: :date, order: :desc) with
      # ActiveRecord 8. ActiveRecord 7 does not allow to control the batch
      # ordering and orders on the primary key unconditionally.
      offset = 0
      window = 100
      while result.length < limit
        items = 0
        candidates.offset(offset).limit(window).each do |job|
          key = [job.suite, job.arch].join('/')
          current_job = current[key]

          if current_job && current_job.status != job.status
            result << current_job
          end

          break if result.size >= limit

          current[key] = job
          items += 1
        end
        offset += window

        break if items == 0
      end
      result
    end

    # Returns an Array of statuses where this package is failing or neutral.
    def fail_or_neutral
      status.flatten.compact.select { |p| (p.status.to_sym == :fail) || (p.status.to_sym == :neutral) }
    end

    def to_s
      # :nodoc:
      "<Package #{name}>"
    end

    def to_str
      # :nodoc:
      name
    end

    def self.prefixes
      # FIXME: optimize(?)
      select(:name).distinct.pluck(:name).map { |n| prefix(n) }.sort.uniq
    end

    def self.prefix(name)
      name =~ /^((lib)?.)/
      Regexp.last_match(1)
    end

    def prefix
      self.class.prefix(name)
    end

    def reject_listed?(params = {})
      Debci.reject_list.include?(name, params)
    end

    def reject_list_comment(params = {})
      Debci.reject_list.comment(name, params)
    end

    def last_updated_at(suite = nil)
      statuses = status.flatten.compact.select { |s| s.suite == suite || !suite }
      statuses.map(&:date).compact.max
    end
  end
end