module Logic::Signup
  module Provider

    def create_buyer_possible?
      account_plans.stock.present?
    end

    def signup_enabled?
      try(:settings).try!(:signups_enabled?) && (account_plans.published.present? || account_plans.default)
    end

    def enable_signup!
      settings.update_attribute(:signups_enabled, true)
      self
    end

    def disable_signup!
      settings.update_attribute(:signups_enabled, false)
      self
    end

    def signup_with_plans(plans, validate_fields = true, defaults = {})
      buyer = self.buyers.build_with_fields
      buyer.validate_fields! if validate_fields
      buyer.provider_account = self
      buyer.buyer = true

      user = buyer.users.build_with_fields
      user.role = :admin
      user.validate_fields! if validate_fields
      user.account = buyer

      yield(buyer, user) if block_given?

      plans = PlansWithDefaults.new(self, plans) do |error|
        buyer.errors.add :plans, error
      end

      return nil unless buyer.valid? && user.valid? && plans.valid?

      #  TODO: concurrency issue (already had it on remonster -
      #  buyer or user can become invalid during the process
      #
      self.class.transaction do
        buyer.save!

        plans.each do |plan|
          attrs = [defaults[plan.class]].compact
          plan.create_contract_with!(buyer, *attrs)
        end

        publish_related_event!(buyer, user)
      end

      true
    end

    private

    def publish_related_event!(account, user)
      event = Accounts::AccountCreatedEvent.create(account, user)
      Rails.application.config.event_store.publish_event(event)
    end
  end

  private

  # It is like Hash but it has provider
  # and encapsulates default plan finding and adding logic
  class PlansWithDefaults
    attr_reader :provider, :errors
    attr_accessor :error_proc

    def initialize(provider, plans = nil, &error_proc)
      @provider = provider
      @error_proc = error_proc
      @plans = Hash.new{ |k,v| k[v] = [] }
      self.selected = plans if plans
    end

    def selected= plans
      self.plans.update plans.group_by(&:class) if plans.present?
      add_default_plans_unless_present!
    end

    delegate :[], to: :plans
    delegate :to_a, to: :values

    def validate
      account_plan = plans[AccountPlan].first
      service_plans = plans[ServicePlan].group_by(&:issuer)
      application_plans = plans[ApplicationPlan].group_by(&:issuer)

      @errors = []

      unless plans[AccountPlan].size == 1
        @errors << "#{AccountPlan.model_name.human} is required"
      end

      unless account_plan.try!(:issuer) == provider
        @errors << "Issuer of #{AccountPlan.model_name.human} must be #{provider.org_name}"
      end

      application_plans.keys.each do |issuer|
        @errors << "Couldn't find a Service Plan for #{issuer.name}. Please contact the API administrator to fix this." unless service_plans[issuer].present?
      end

      service_plans.values.each do |plans|
        @errors << "Can subscribe only one plan per service" if plans.size > 1
      end

      @errors.each &@error_proc if @error_proc

      @errors.presence
    end

    def errors?
      validate if @errors.nil?
      @errors.present?
    end

    def valid?
      !errors?
    end

    def to_a
      plans[AccountPlan] + plans[ServicePlan] + plans[ApplicationPlan]
    end

    delegate :each, to: :to_a

    protected

    attr_reader :plans

    def contract_first_published_service_plan?
      !service_plans_enabled? && provider.provider_can_use?(:published_service_plan_signup)
    end

    def service_plans_enabled?
      provider.settings.service_plans_ui_visible?
    end

    def add_default_plans_unless_present!
      add_default_or_nil(provider, provider.account_plans, AccountPlan)

      provider.services.each do |service|
        # returns nil when there is no default plan and no plan given
        # that means that there is no service plan and cannot be application plan
        unless (has_service_plan = add_default_or_nil(service, service.service_plans, ServicePlan))
          Rails.logger.debug("Skipping application plan in signup, because there is no service plan")
          next if service_plans_enabled?
        end

        if add_default_or_nil(service, service.application_plans, ApplicationPlan)
          next unless contract_first_published_service_plan?

          if !has_service_plan && (first_service_plan = service.service_plans.published.first)
            plans[ServicePlan] << first_service_plan
          end
        end
      end
    end

    # returns true if there is already plan of same type and issuer
    # or adds default plan to array and returns the array
    def add_default_or_nil(issuer, plans, type)
      return true if self.plans[type].any? {|plan| plan.issuer == issuer }

      if (default = plans.default_or_nil)
        self.plans[type] << default
      end
    end

  end

end
