module Katello
  class DockerMetaTag < Katello::Model
    include ScopedSearchExtensions

    has_many :repository_docker_meta_tags, :class_name => "Katello::RepositoryDockerMetaTag", :dependent => :delete_all, :inverse_of => :docker_meta_tag
    has_many :repositories, :through => :repository_docker_meta_tags, :inverse_of => :docker_meta_tags
    has_many :root_repositories, through: :repositories, :source => :root, class_name: "Katello::RootRepository"
    belongs_to :schema1, :class_name => "Katello::DockerTag",
                          :inverse_of => :schema1_meta_tag

    belongs_to :schema2, :class_name => "Katello::DockerTag",
                          :inverse_of => :schema2_meta_tag

    scoped_search :on => :name, :complete_value => true, :rename => :tag
    scoped_search :on => :id, :only_explicit => true
    scoped_search :on => :name, :complete_value => true
    scoped_search :on => :schema_version, :rename => :schema_version, :complete_value => { "1" => "1", "2" => "2"},
                  :only_explicit => true, :ext_method => :find_by_schema_version, :operators => ["="]
    scoped_search :relation => :repository, :on => :container_repository_name, :rename => :image,
                  :complete_value => true, :only_explicit => true
    scoped_search :on => :digest, :rename => :digest, :complete_value => false,
                  :only_explicit => true, :ext_method => :find_by_digest, :operators => ["="]
    scoped_search :relation => :root_repositories, :on => :name, :rename => :repository, :complete_value => true,
                  :ext_method => :search_by_repo_name, :only_explicit => true

    def self.repository_association_class
      RepositoryDockerMetaTag
    end

    def repository
      repositories.first
    end

    def upstream_name
      repository.docker_upstream_name
    end

    def self.search_by_repo_name(_key, operator, value)
      conditions = sanitize_sql_for_conditions(["#{Katello::RootRepository.table_name}.name #{operator} ?", value_to_sql(operator, value)])
      query = self.joins(:repositories => :root).where(conditions).select('id')
      {:conditions => "#{self.table_name}.id IN (#{query.to_sql})"}
    end

    def self.meta_tags_by_digest_query(operator, digest, manifest_klass)
      search = "digest #{operator} ?"
      query = DockerMetaTag.search_in_tags(DockerTag.where(:docker_taggable_type => manifest_klass.name,
                                                           :docker_taggable_id => manifest_klass.where(search, digest))).select(:id).to_sql
      "#{DockerMetaTag.table_name}.id in (#{query})"
    end

    def self.find_by_digest(_key, operator, value)
      if operator == '='
        conditions = meta_tags_by_digest_query(operator, value, DockerManifest) + " OR " + meta_tags_by_digest_query(operator, value, DockerManifestList)
      else
        #failure condition. No such value so must return 0
        conditions = "1=0"
      end
      { :conditions => conditions }
    end

    def self.find_by_schema_version(_key, operator, value)
      conditions = ""
      if operator == '='
        if ["1", "2"].include?(value)
          column = if value == "1"
                     "schema1_id"
                   else
                     "schema2_id"
                   end
          docker_meta_tag_arel_table = ::Katello::DockerMetaTag.arel_table
          conditions = docker_meta_tag_arel_table[column].not_eq(nil).to_sql
        else
          #failure condition. No such value so must return 0
          conditions = "1=0"
        end
      end
      { :conditions => conditions }
    end

    def self.delegate_to_tags(*names)
      names.each do |name|
        define_method(name) do
          if schema2
            schema2.send(name)
          else
            schema1.send(name)
          end
        end
      end
    end

    delegate_to_tags :docker_manifest
    delegate_to_tags :product, :environment, :content_view_version

    def related_tags
      self.class.where(:id => ::Katello::RepositoryDockerMetaTag.where(:repository_id => repositories.first.group).
                       select(:docker_meta_tag_id), :name => name)
    end

    def self.in_repositories(repos, grouped = false)
      if grouped
        search_in_tags(DockerTag.in_repositories(repos), grouped)
      else
        search_in_tags(DockerTag.in_repositories(repos)).where(:id => repository_association_class.where(:repository_id => repos).select(:docker_meta_tag_id))
      end
    end

    def self.search_in_tags(tags, grouped = false)
      sql = tags.select("#{::Katello::DockerTag.table_name}.id").to_sql

      if grouped
        grouped_fields = "#{table_name}.name, katello_repositories.root_id"
        ids = distinct.select("ON (#{grouped_fields}) #{table_name}.id").joins(:repositories)
        self.where(:id => ids).where("#{self.table_name}.schema1_id in (#{sql}) or #{self.table_name}.schema2_id in (#{sql})")
      else
        self.where("#{self.table_name}.schema1_id in (#{sql}) or #{self.table_name}.schema2_id in (#{sql})")
      end
    end

    def schema1_manifest
      schema1.try(:docker_taggable)
    end

    def schema2_manifest
      schema2.try(:docker_taggable)
    end

    def self.with_pulp_id(ids)
      self.with_identifiers(ids)
    end

    def self.with_identifiers(ids)
      self.where(:id => ids)
    end

    def self.cleanup_tags
      #Cleanup RepositoryMetaTag for nil schema meta tags
      RepositoryDockerMetaTag.where(docker_meta_tag: where(schema1: nil, schema2: nil)).delete_all
      self.where.not(id: RepositoryDockerMetaTag.select(:docker_meta_tag_id)).or(where(schema1: nil, schema2: nil)).delete_all
    end

    def self.import_meta_tags(repositories)
      repositories.each do |repo|
        tag_table_values = get_tag_table_values(repo)
        meta_tag_table_values = DockerMetaTag.where(:id => repository_association_class.where(:repository_id => repo.id).select(:docker_meta_tag_id)).
                                  select(:schema1_id, :schema2_id, :name).map do |meta_tag|
          [meta_tag.schema1_id, meta_tag.schema2_id, meta_tag.name]
        end

        # Delete [meta_tag_table_values - tag_table_values], insert [tag_table_values - meta_tag_table_values]

        docker_meta_tag_arel_table = ::Katello::DockerMetaTag.arel_table
        params_to_query_for_delete = (meta_tag_table_values - tag_table_values).map do |schema1, schema2, name|
          conditional = docker_meta_tag_arel_table[:schema1_id].eq(schema1).and(
                        docker_meta_tag_arel_table[:schema2_id].eq(schema2)).and(
                        docker_meta_tag_arel_table[:name].eq(name)).to_sql

          "(#{conditional})"
        end

        unless params_to_query_for_delete.empty?
          RepositoryDockerMetaTag.where(:repository_id => repo.id).where(:docker_meta_tag => DockerMetaTag.where(params_to_query_for_delete.join(" OR "))).delete_all
        end

        metatags = []
        (tag_table_values - meta_tag_table_values).each do |schema1, schema2, name|
          metatags << DockerMetaTag.where(:schema1_id => schema1,
                              :schema2_id => schema2,
                              :name => name).first_or_create
        end
        repo.docker_meta_tags += metatags
      end
    end

    def self.get_tag_table_values(repo)
      # queries DockerTags for a repo and retuns a [schema1, schema2 , name] tuple combination
      tags = ::Katello::DockerTag.where(:id => RepositoryDockerTag.
                                        where(:repository_id => repo.id).
                                        select(:docker_tag_id))
      dups = tags.group_by(&:name)

      dups.map do |name, values|
        if values.first.docker_manifest.schema_version == 1
          schema1, schema2 = values
        else
          schema2, schema1 = values
        end
        [schema1.try(:id), schema2.try(:id), name]
      end
    end
  end
end
