# IMPORTANT: When adding a new plan, add it to both THREESCALE_LIMITS
# and THREESCALE_PLANS
module Logic::ProviderUpgrade
  ENTERPRISE_PLAN = 'enterprise'.freeze
  ENTERPRISE_PRODUCT = 'enterprise'.freeze

  PLAN_2016_PERSONAL = '2014_developer_copy_14540162559051023'.freeze
  PLAN_2016_TRIAL = '2015_plus_copy_14540190951550343'.freeze

  SWITCHES = [
    SWITCH_FINANCE = :finance,
    SWITCH_MULTIPLE_APPLICATIONS = :multiple_applications,
    SWITCH_MULTIPLE_SERVICES = :multiple_services,
    SWITCH_MULTIPLE_USERS = :multiple_users,
    SWITCH_ACCOUNT_PLANS = :account_plans,
    SWITCH_GROUPS = :groups,
    SWITCH_LOG_REQUESTS = :log_requests,
    SWITCH_END_USERS = :end_users,
    SWITCH_SERVICE_PLANS = :service_plans,
    SWITCH_BRANDING = :branding,
    SWITCH_SKIP_EMAIL_ENGAGEMENT_FOOTER = :skip_email_engagement_footer,
    SWITCH_WEB_HOOKS = :web_hooks,
    SWITCH_AIM_TOOLS = :iam_tools,
    SWITCH_REQUIRE_CC_ON_PAID_PLAN = :require_cc_on_signup
  ].freeze

  PLUS_SWITCHES = [SWITCH_FINANCE, SWITCH_MULTIPLE_APPLICATIONS, SWITCH_BRANDING, SWITCH_REQUIRE_CC_ON_PAID_PLAN].freeze
  POWER_SWITCHES = PLUS_SWITCHES + [SWITCH_ACCOUNT_PLANS, SWITCH_MULTIPLE_USERS, SWITCH_GROUPS].freeze
  PRO_SWITCHES = POWER_SWITCHES + [SWITCH_LOG_REQUESTS, SWITCH_END_USERS, SWITCH_MULTIPLE_SERVICES, SWITCH_SERVICE_PLANS].freeze
  ENTERPRISE_SWITCHES = PRO_SWITCHES + [SWITCH_SKIP_EMAIL_ENGAGEMENT_FOOTER, SWITCH_WEB_HOOKS, SWITCH_AIM_TOOLS].freeze
  PRO_2017_SWITCHES = PRO_SWITCHES + [SWITCH_WEB_HOOKS] - [SWITCH_LOG_REQUESTS, SWITCH_END_USERS]

  # we use dashes in names inside of these two hashes because of the Heroku policy:

  HEROKU_PLANS = {
    heroku_developer: [],
    :'he-roku-personal' => [],
    heroku_plus: PLUS_SWITCHES,
    :'he-roku-base' => PLUS_SWITCHES,
    heroku_power: POWER_SWITCHES - [SWITCH_MULTIPLE_USERS],
    heroku_pro: PRO_SWITCHES - [SWITCH_MULTIPLE_USERS],
    heroku_enterprise: ENTERPRISE_SWITCHES - [SWITCH_MULTIPLE_USERS]
  }.freeze

  APPDIRECT_PLANS = {
    free_appdirect: [],
    basic_appdirect: PLUS_SWITCHES,
    :'power-appdirect' => POWER_SWITCHES - [SWITCH_MULTIPLE_USERS],
    pro_appdirect: PRO_SWITCHES - [SWITCH_MULTIPLE_USERS],
    enterprise_appdirect: ENTERPRISE_SWITCHES - [SWITCH_MULTIPLE_USERS]
  }.freeze

  PARTNERS_PLANS = {
    heroku: HEROKU_PLANS,
    appdirect: APPDIRECT_PLANS
  }.freeze

  THREESCALE_VISIBLE_SWITCHES = [
    SWITCH_FINANCE, SWITCH_BRANDING, SWITCH_END_USERS, SWITCH_END_USERS,
    SWITCH_GROUPS, SWITCH_SKIP_EMAIL_ENGAGEMENT_FOOTER, SWITCH_WEB_HOOKS, SWITCH_LOG_REQUESTS,
    SWITCH_REQUIRE_CC_ON_PAID_PLAN
  ].freeze

  MULTISERVICE_MAX_SERVICES = 3
  BASE_LIMITS = { max_users: 1, max_services: 1 }.freeze
  MULTISERVICE_LIMITS = { max_users: 5, max_services: MULTISERVICE_MAX_SERVICES }.freeze
  ENTERPRISE_LIMITS = { max_users: nil, max_services: nil }.freeze

  THREESCALE_RULES = {
    'base' => { switches: base = [], limits: BASE_LIMITS },
    PLAN_2016_PERSONAL => { switches: base, limits: BASE_LIMITS },
    PLAN_2016_TRIAL => { switches: base, limits: BASE_LIMITS },
    '2014_developer' => { switches: base, limits: BASE_LIMITS },
    '2014_developer_branding_offer_2014' => { switches: branding = base + [SWITCH_BRANDING], limits: BASE_LIMITS },
    '2015_plus' => { switches: branding, limits: BASE_LIMITS },
    '2015_plus_copy_14647420901501482' => { switches: branding, limits: BASE_LIMITS },
    'plus' => { switches: PLUS_SWITCHES, limits: BASE_LIMITS },
    '2015_plus_copy_1450093863689082' => { switches: PLUS_SWITCHES, limits: BASE_LIMITS },
    'power1M' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    'power2M' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    'power3M' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    'power5M' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    '2014_power_250k' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    '2014_power_500k' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    '2014_power_750k' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    '2014_power_1M' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    '2014_power_250k_copy_14500940100801477' => { switches: POWER_SWITCHES, limits: BASE_LIMITS },
    'pro3M' => { switches: PRO_SWITCHES, limits: MULTISERVICE_LIMITS },
    'pro5M' => { switches: PRO_SWITCHES, limits: MULTISERVICE_LIMITS },
    'pro10M' => { switches: PRO_SWITCHES, limits: MULTISERVICE_LIMITS },
    'pro20M' => { switches: PRO_SWITCHES, limits: MULTISERVICE_LIMITS },
    '2014_pro_1M' => { switches: PRO_SWITCHES, limits: MULTISERVICE_LIMITS },
    '2014_pro_2M' => { switches: PRO_SWITCHES, limits: MULTISERVICE_LIMITS },
    '2014_pro_3M' => { switches: PRO_SWITCHES, limits: MULTISERVICE_LIMITS },
    'enterprise' => { switches: ENTERPRISE_SWITCHES, limits: ENTERPRISE_LIMITS },
    '2014_enterprise_3M' => { switches: ENTERPRISE_SWITCHES, limits: ENTERPRISE_LIMITS },
    '2017-pro-500k' => { switches: PRO_2017_SWITCHES, limits: MULTISERVICE_LIMITS },
    '2014_enterprise_3M_copy_14453385826818197' => { switches: ENTERPRISE_SWITCHES, limits: ENTERPRISE_LIMITS },
    '2014_enterprise_3M_copy_14884527747675161' => { switches: ENTERPRISE_SWITCHES, limits: ENTERPRISE_LIMITS },
    '2014_enterprise_3M_copy_14884529403257391' => { switches: ENTERPRISE_SWITCHES, limits: ENTERPRISE_LIMITS },
    '2014_enterprise_3M_copy_14884529494425366' => { switches: ENTERPRISE_SWITCHES, limits: ENTERPRISE_LIMITS },
    '2014_enterprise_3M_copy_14884529512328804' => { switches: ENTERPRISE_SWITCHES, limits: ENTERPRISE_LIMITS }
  }.freeze

  THREESCALE_LIMITS = THREESCALE_RULES.map { |plan, rules| [plan.freeze, rules.fetch(:limits)] }.to_h.freeze
  THREESCALE_PLANS = THREESCALE_RULES.map { |plan, rules| [plan.freeze, rules.fetch(:switches)] }.to_h.freeze
  module Provider
    # @return [Hash<Symbol,Array<Symbol>>]
    def available_plans
      if partner.present?
        PARTNERS_PLANS[partner.system_name.to_sym] || {}
      else
        THREESCALE_PLANS
      end
    end

    # @return [::Array<::Settings::Switch>]
    def available_switches
      switch_names = available_plans.values.flatten.uniq.presence || SWITCHES

      switch_names.map do |switch_name|
        settings.switches.fetch(switch_name)
      end
    end

    def upgrade_to_provider_plan!(new_plan)
      # consider just returning instead as Someone can mangle the
      # input to trigger it
      raise 'Missing credit card!' unless self.credit_card_stored?
      raise "Cannot upgrade to plan #{new_plan.system_name}" unless can_upgrade_to?(new_plan)
      force_upgrade_to_provider_plan!(new_plan)
    end

    # intended to be called from console
    # only active and show switches
    def force_upgrade_to_provider_plan!(new_plan)
      new_plan = change_plan!(new_plan)

      switches_on = switches_for_plan(new_plan)
      make_switches_on(switches_on)

      move_limits_to(new_plan)
    end

    # intended to be called for partners stuff and console
    def force_to_change_plan!(new_plan)
      new_plan = change_plan!(new_plan)
      switches_on = switches_for_plan(new_plan)
      make_switches_on(switches_on)
      make_switches_off(switches_on)
    end

    def hideable_switches
      settings.switches.select do |name, switch|
        THREESCALE_VISIBLE_SWITCHES.exclude?(name.to_s) && switch.hideable?
      end
    end

    def has_best_plan?
      plan = bought_cinstance.plan
      max_index = available_plans.size - 1

      max_index == available_plans.keys.index(plan.system_name)
    end

    def can_upgrade_by_email?(plan)
      plan.system_name.include?(ENTERPRISE_PLAN) &&
        bought_cinstance.plan.system_name.exclude?(ENTERPRISE_PLAN)
    end

    def can_upgrade_to?(plan)
      return false if plan.system_name.include? ENTERPRISE_PLAN

      bought_plan = bought_cinstance.plan
      old = if bought_plan.customized?
              bought_plan.original
            else
              bought_plan
            end

      from = available_plans.keys.index(old.system_name) || -1
      to = available_plans.keys.index(plan.system_name)

      to && (from < to)
    end

    def first_plan_with_switch(switch)
      master_published_plans = Account.master.application_plans.published
      available_plans.each do |key, value|
        if value.include?(switch.to_sym)
          plan = master_published_plans.find_by_system_name(key)
          return plan if plan
        end
      end
      nil
    end

    def limits_for_plan(plan)
      THREESCALE_LIMITS[plan.system_name] || {}
    end

    module_function :limits_for_plan

    def update_provider_constraints_to(limits, comment)
      new_limits = limits.merge(provider: self, audit_comment: comment)
      # assigning to the model, because #provider_constraints
      # would return different null object every time it is called
      # so if this method would be called twice,
      # it would try to create second constraints instead of updating the existing
      self.provider_constraints = constraints = provider_constraints
      constraints.update_attributes(new_limits)
    end

    private

    def change_plan!(new_plan)
      if new_plan.is_a? String
        new_plan = Account.master.application_plans.find_by_system_name(new_plan)
        raise "Unknown application plan. Should be one of #{available_plans.keys.join(', ')}" unless new_plan
      end

      bought_cinstance.change_plan!(new_plan)
      enterprise_plan_product(new_plan)
      new_plan
    end

    def enterprise_plan_product(new_plan)
      return unless new_plan.system_name.include? ENTERPRISE_PLAN
      settings.update_attribute :product, ENTERPRISE_PRODUCT
    end

    def switches_for_plan(plan)
      available_plans[plan.system_name] || []
    end

    def move_limits_to(new_plan)
      limits = limits_for_plan(new_plan).presence
      return unless limits
      update_provider_constraints_to(limits, "Upgrading limits to match plan #{new_plan.system_name}")
    end

    def make_switches_on(switches_on)
      switches_on.reject(&method(:allowed_switch?)).each do |switch|
        settings.public_send("allow_#{switch}!")
        show_switch!(switch)
      end
    end

    def show_switch!(switch)
      return unless THREESCALE_VISIBLE_SWITCHES.include?(switch)
      show_switch_method = settings.public_method("show_#{switch}!")
      if switch == :require_cc_on_signup
        show_switch_method.call unless provider_can_use?(switch)
      else
        show_switch_method.call
      end
    end

    def allowed_switch?(switch)
      settings.public_send(switch).allowed?
    end

    def make_switches_off(switches_on)
      switches_off = SWITCHES - switches_on
      switches_off.each do |switch|
        next if settings.send(switch).denied?
        if THREESCALE_VISIBLE_SWITCHES.include?(switch)
          settings.send("hide_#{switch}!")
        end

        settings.send("deny_#{switch}!")
      end
    end
  end
end
