require 'fake_braintree/helpers'

module FakeBraintree
  class Subscription
    include Helpers

    def initialize(subscription_hash_from_params, options)
      id = subscription_hash_from_params['id'] || options[:id]
      @subscription_hash = subscription_hash_from_params.merge(
        'merchant_id' => options[:merchant_id],
        'id' => id
      )
      set_subscription_id
      set_subscription_status
    end

    def create
      @subscription_hash['created_at'] = Time.now
      create_subscription_with(subscription_hash)
      if credit_card = FakeBraintree.registry.credit_cards[payment_method_token]
        credit_card['subscriptions'] ||= []
        credit_card['subscriptions'] << subscription_hash
      end
      response_for_created_subscription(subscription_hash)
    end

    def update
      if subscription_exists_in_registry?
        updated_subscription = update_existing_subscription(subscription_hash)
        response_for_created_subscription(updated_subscription)
      else
        response_for_subscription_not_found
      end
    end

    def cancel
      if subscription_exists_in_registry?
        canceled_subscription = update_existing_subscription('status' => canceled_status)
        response_for_canceled_subscription(canceled_subscription)
      else
        response_for_subscription_not_found
      end
    end

    private

    def subscription_hash
      @subscription_hash.merge(
        'transactions' => [],
        'add_ons' => add_ons,
        'discounts' => discounts,
        'next_billing_date' => braintree_formatted_date(next_billing_date),
        'billing_day_of_month' => billing_day_of_month,
        'billing_period_start_date' => braintree_formatted_date(billing_period_start_date),
        'billing_period_end_date' => braintree_formatted_date(billing_period_end_date)
      )
    end

    def update_existing_subscription(updates)
      updated_subscription = subscription_from_registry.merge(updates)
      FakeBraintree.registry.subscriptions[subscription_id] = updated_subscription
    end

    def create_subscription_with(new_subscription_hash)
      FakeBraintree.registry.subscriptions[new_subscription_hash['id'].to_s] = new_subscription_hash
    end

    def subscription_from_registry
      FakeBraintree.registry.subscriptions[subscription_id]
    end

    def subscription_exists_in_registry?
      FakeBraintree.registry.subscriptions.key?(subscription_id)
    end

    def braintree_formatted_date(date)
      date.strftime('%Y-%m-%d')
    end

    def add_ons
      discounts_or_add_ons(@subscription_hash['add_ons'])
    end

    def discounts
      discounts_or_add_ons(@subscription_hash['discounts'])
    end

    def discounts_or_add_ons(discount_or_add_on)
      return [] unless discount_or_add_on.is_a?(Hash)

      if Array(discount_or_add_on['add']).any?
        discount_or_add_on['add'].map do |hsh|
          {
            'id'       => hsh['inherited_from_id'],
            'quantity' => hsh['quantity'],
            'amount'   => hsh['amount']
          }
        end
      elsif Array(discount_or_add_on['update']).any?
        discount_or_add_on['update'].map do |hsh|
          {
            'id'       => hsh['existing_id'],
            'quantity' => hsh['quantity'],
            'amount'   => hsh['amount']
          }
        end
      else
        []
      end
    end

    def next_billing_date
      billing_period_start_date + 1.month
    end

    def billing_day_of_month
      next_billing_date.mday > 28 ? 31 : next_billing_date.mday
    end

    def billing_period_start_date
      @billing_period_start_date ||= Date.today
    end

    def billing_period_end_date
      next_billing_date - 1.day
    end

    def set_subscription_id
      @subscription_hash['id'] ||= generate_new_subscription_id
    end

    def set_subscription_status
      @subscription_hash['status'] ||= active_status
    end

    def subscription_id
      subscription_hash['id']
    end

    def generate_new_subscription_id
      md5("#{payment_method_token}#{Time.now.to_f}")[0,6]
    end

    def payment_method_token
      @subscription_hash['payment_method_token']
    end

    def active_status
      Braintree::Subscription::Status::Active
    end

    def canceled_status
      Braintree::Subscription::Status::Canceled
    end

    def response_for_created_subscription(hash)
      gzipped_response(201, hash.to_xml(root: 'subscription'))
    end

    def response_for_subscription_not_found
      gzipped_response(404, {})
    end

    def response_for_canceled_subscription(hash)
      gzipped_response(200, hash.to_xml(root: 'subscription'))
    end
  end
end
