require "pact_broker/dataset"
require "pact_broker/pacts/content"
require "pact_broker/pacts/pact_version_association_loaders"

module PactBroker
  module Pacts
    class PactVersion < Sequel::Model(:pact_versions)
      include PactVersionAssociationLoaders
      set_primary_key :id

      plugin :timestamps
      plugin :upsert, identifying_columns: [:consumer_id, :provider_id, :sha]

      one_to_many(:pact_publications, reciprocal: :pact_version)
      one_to_many(:verifications, reciprocal: :verification, order: :id, class: "PactBroker::Domain::Verification")
      many_to_one(:provider, class: "PactBroker::Domain::Pacticipant", key: :provider_id, primary_key: :id)
      many_to_one(:consumer, class: "PactBroker::Domain::Pacticipant", key: :consumer_id, primary_key: :id)
      many_to_many(:consumer_versions,
        class: "PactBroker::Domain::Version",
        join_table: :pact_publications,
        left_key: :pact_version_id,
        right_key: :consumer_version_id,
        order: :order
      )

      one_to_one(:latest_main_branch_verification,
        class: "PactBroker::Domain::Verification",
        read_only: true,
        dataset: LATEST_MAIN_BRANCH_VERIFICATION,
        key: :pact_version_id,
        primary_key: :id,
        eager_block: lambda { | ds | ds.from_provider_main_branch.latest_by_pact_version },
        allow_eager: true
      )

      one_to_one(:latest_verification,
        class: "PactBroker::Domain::Verification",
        read_only: true,
        dataset: LATEST_VERIFICATION_DATASET,
        key: :pact_version_id, primary_key: :id,
        eager_block: lambda { | ds | ds.latest_by_pact_version },
        allow_eager: true
      )

      # do not eager load this - it won't work because of the limit(1)
      one_through_one(:latest_consumer_version,
        class: "PactBroker::Domain::Version",
        join_table: :pact_publications,
        left_key: :pact_version_id,
        right_key: :consumer_version_id, &LATEST_CONSUMER_VERSION_LAZY_LOADER
      )

      dataset_module do
        include PactBroker::Dataset

        def for_pact_domain(pact_domain)
          where(
            sha: pact_domain.pact_version_sha,
            consumer_id: pact_domain.consumer.id,
            provider_id: pact_domain.provider.id
          ).single_record
        end

        def join_successful_verifications
          verifications_join = {
            Sequel[:verifications][:pact_version_id] => Sequel[:pact_versions][:id],
            Sequel[:verifications][:success] => true
          }
          join(:verifications, verifications_join)
        end

        def join_provider_versions
          join(:versions, { Sequel[:provider_versions][:id] => Sequel[:verifications][:provider_version_id] }, { table_alias: :provider_versions })
        end

        def join_provider_version_tags_for_tag(tag)
          tags_join = {
            Sequel[:tags][:version_id] => Sequel[:provider_versions][:id],
            Sequel[:tags][:name] => tag
          }
          join(:tags, tags_join)
        end

        def join_branch_versions_for_branch_name_to_verifications(branch_name)
          branch_versions_join = {
            Sequel[:branch_versions][:version_id] => Sequel[:verifications][:provider_version_id],
            Sequel[:branch_versions][:branch_name] => branch_name
          }
          join(:branch_versions, branch_versions_join)
        end
      end

      def name
        "Pact between #{consumer_name} and #{provider_name}"
      end

      def provider_name
        provider.name
      end

      def consumer_name
        consumer.name
      end

      def latest_pact_publication
        PactBroker::Pacts::PactPublication
          .for_pact_version_id(id)
          .remove_overridden_revisions_from_complete_query
          .latest || PactBroker::Pacts::PactPublication.for_pact_version_id(id).latest
      end


      def latest_consumer_version_number
        latest_consumer_version.number
      end

      def pending_for_provider_branch?(branch_name)
        !any_successful_verifications_from_provider_branch?(branch_name) && !any_successful_verifications_from_another_branch_from_before_this_branch_created?(branch_name)
      end

      def select_provider_tags_with_successful_verifications_from_another_branch_from_before_this_branch_created(tags)
        tags.select do | tag |
          first_tag_with_name = PactBroker::Domain::Tag.where(pacticipant_id: provider_id, name: tag).order(:created_at).first

          verifications_join = {
            Sequel[:verifications][:pact_version_id] => Sequel[:pact_versions][:id],
            Sequel[:verifications][:success] => true
          }
          tags_join = {
            Sequel[:tags][:version_id] => Sequel[:versions][:id],
          }
          query = PactVersion.where(Sequel[:pact_versions][:id] => id)
            .join(:verifications, verifications_join)
            .join(:versions, Sequel[:versions][:id] => Sequel[:verifications][:provider_version_id])
            .join(:tags, tags_join) do
              Sequel.lit("tags.name != ?", tag)
            end

          if first_tag_with_name
            query = query.where { Sequel[:verifications][:created_at] < first_tag_with_name.created_at }
          end

          query.any?
        end
      end

      def select_provider_tags_with_successful_verifications(tags)
        tags.select do | tag |
          PactVersion.where(Sequel[:pact_versions][:id] => id)
            .join_successful_verifications
            .join_provider_versions
            .join_provider_version_tags_for_tag(tag)
            .any?
        end
      end

      def verified_successfully_by_any_provider_version?
        PactVersion.where(Sequel[:pact_versions][:id] => id)
          .join_successful_verifications
          .any?
      end

      def set_interactions_and_messages_counts!
        if interactions_count.nil? || messages_count.nil?
          content_object = PactBroker::Pacts::Content.from_json(content)
          update(
            messages_count: content_object.messages&.count || 0,
            interactions_count: content_object.interactions&.count || 0
          )
        end
      end

      def any_successful_verifications_from_provider_branch?(branch_name)
        PactVersion.where(Sequel[:pact_versions][:id] => id)
          .join_successful_verifications
          .join_branch_versions_for_branch_name_to_verifications(branch_name)
          .any?
      end

      def any_successful_verifications_from_another_branch_from_before_this_branch_created?(branch_name)
        branch = PactBroker::Versions::Branch.where(pacticipant_id: provider_id, name: branch_name).single_record

        verifications_join = {
          Sequel[:verifications][:pact_version_id] => Sequel[:pact_versions][:id],
          Sequel[:verifications][:success] => true
        }
        query = PactVersion.where(Sequel[:pact_versions][:id] => id)
          .join(:verifications, verifications_join)
          .join(:branch_versions, Sequel[:branch_versions][:version_id] => Sequel[:verifications][:provider_version_id]) do
            Sequel.lit("branch_versions.branch_name != ?", branch_name)
          end

        if branch
          query = query.where { Sequel[:verifications][:created_at] < branch.created_at }
        end

        query.any?
      end
    end
  end
end

# Table: pact_versions
# Columns:
#  id                 | integer                     | PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY
#  consumer_id        | integer                     | NOT NULL
#  provider_id        | integer                     | NOT NULL
#  sha                | text                        | NOT NULL
#  content            | text                        |
#  created_at         | timestamp without time zone | NOT NULL
#  messages_count     | integer                     |
#  interactions_count | integer                     |
# Indexes:
#  pact_versions_pkey   | PRIMARY KEY btree (id)
#  unq_pvc_con_prov_sha | UNIQUE btree (consumer_id, provider_id, sha)
# Foreign key constraints:
#  pact_versions_consumer_id_fkey | (consumer_id) REFERENCES pacticipants(id)
#  pact_versions_provider_id_fkey | (provider_id) REFERENCES pacticipants(id)
# Referenced By:
#  latest_pact_publication_ids_for_consumer_versions            | latest_pact_publication_ids_for_consumer_v_pact_version_id_fkey | (pact_version_id) REFERENCES pact_versions(id) ON DELETE CASCADE
#  latest_verification_id_for_pact_version_and_provider_version | latest_v_id_for_pv_and_pv_pact_version_id_fk                    | (pact_version_id) REFERENCES pact_versions(id) ON DELETE CASCADE
#  pact_publications                                            | pact_publications_pact_version_id_fkey                          | (pact_version_id) REFERENCES pact_versions(id)
#  pact_version_provider_tag_successful_verifications           | pact_version_provider_tag_successful_verifications_pv_id_fk     | (pact_version_id) REFERENCES pact_versions(id) ON DELETE CASCADE
#  verifications                                                | verifications_pact_version_id_fkey                              | (pact_version_id) REFERENCES pact_versions(id)
