module Saucy
  module Account
    extend ActiveSupport::Concern

    included do
      require "rubygems"
      require "braintree"

      CUSTOMER_ATTRIBUTES = { :cardholder_name => :cardholder_name, 
                              :billing_email => :email, 
                              :card_number => :number, 
                              :expiration_month => :expiration_month, 
                              :expiration_year => :expiration_year, 
                              :verification_code => :cvv }

      extend ActiveSupport::Memoizable
      has_many :memberships, :dependent => :destroy
      has_many :users, :through => :memberships
      has_many :projects, :dependent => :destroy
      has_many :admins, :through    => :memberships,
                        :source     => :user,
                        :conditions => { 'memberships.admin' => true }

      belongs_to :plan

      delegate :free?, :billed?, :to => :plan

      validates_uniqueness_of :name, :keyword
      validates_presence_of :name, :keyword, :plan_id

      attr_accessor *CUSTOMER_ATTRIBUTES.keys

      attr_accessible :name, :keyword

      validates_format_of :keyword,
                          :with    => %r{^[a-z0-9]+$},
                          :message => "must be only lower case letters."

      before_create :create_customer
      before_create :create_subscription, :if => :billed?

      memoize :customer
      memoize :subscription
    end

    module InstanceMethods
      def to_param
        keyword
      end

      def has_member?(user)
        memberships.exists?(:user_id => user.id)
      end

      def users_by_name
        users.by_name
      end

      def projects_by_name
        projects.by_name
      end

      def projects_visible_to(user)
        projects.visible_to(user)
      end

      def memberships_by_name
        memberships.by_name
      end

      def customer
        Braintree::Customer.find(customer_token) if customer_token
      end

      def credit_card
        customer.credit_cards[0] if customer && customer.credit_cards.any?
      end

      def subscription
        Braintree::Subscription.find(subscription_token) if subscription_token
      end

      def save_braintree!(attributes)
        successful = true
        self.plan = ::Plan.find(attributes[:plan_id]) if attributes[:plan_id].present?
        if CUSTOMER_ATTRIBUTES.keys.any? { |attribute| attributes[attribute].present? }
          CUSTOMER_ATTRIBUTES.keys.each do |attribute|
            send("#{attribute}=", attributes[attribute]) if attributes[attribute].present?
          end
          result = Braintree::Customer.update(customer_token, customer_attributes)
          successful = result.success?
          handle_customer_result(result)
        end
        if successful && attributes[:plan_id].present?
          if subscription 
            Braintree::Subscription.update(subscription_token, :plan_id => attributes[:plan_id])
          else
            create_subscription
          end
          flush_cache :subscription
        end
        successful && save
      end

      def can_change_plan_to?(new_plan)
        plan.limits.where(:value_type => :number).all? do |limit|
          new_plan.limit(limit.name).value >= send(limit.name).count
        end
      end

      def past_due?
        subscription_status == Braintree::Subscription::Status::PastDue
      end

      private

      def customer_attributes
        {
          :email => billing_email,
          :credit_card => credit_card_attributes
        }
      end

      def credit_card_attributes
        if plan.billed?
          card_attributes = { :cardholder_name => cardholder_name,
                              :number => card_number,
                              :expiration_month => expiration_month,
                              :expiration_year => expiration_year,
                              :cvv => verification_code }
          if credit_card
            card_attributes.merge!(:options => credit_card_options)
          end
          card_attributes
        else 
          {}
        end
      end

      def credit_card_options
        if customer && customer.credit_cards.any?
          { :update_existing_token => customer.credit_cards[0].token }
        else
          {}
        end
      end

      def create_customer
        result = Braintree::Customer.create(customer_attributes)
        handle_customer_result(result)
      end

      def handle_customer_result(result)
        if result.success?
          self.customer_token = result.customer.id
          flush_cache :customer
        else
          verification = result.credit_card_verification
          if verification && verification.status == "processor_declined"
            errors[:card_number] << "was denied by the payment processor with the message: #{verification.processor_response_text}"
          elsif verification && verification.status == "gateway_rejected"
            errors[:verification_code] << "did not match"
          elsif result.errors.any?
            result.errors.each do |error|
              if error.attribute == "number"
                errors[:card_number] << error.message.gsub("Credit card number ", "")
              elsif error.attribute == "CVV"
                errors[:verification_code] << error.message.gsub("CVV ", "")
              elsif error.attribute == "expiration_month"
                errors[:expiration_month] << error.message.gsub("Expiration month ", "")
              elsif error.attribute == "expiration_year"
                errors[:expiration_year] << error.message.gsub("Expiration year ", "")
              end
            end
          end
        end
        result.success?
      end

      def create_subscription
        result = Braintree::Subscription.create(
          :payment_method_token => credit_card.token,
          :plan_id => plan_id
        )
        if result.success?
          self.subscription_token = result.subscription.id
          self.next_billing_date = result.subscription.next_billing_date
          self.subscription_status = result.subscription.status
        else
          false
        end
      end
    end

    module ClassMethods
      def update_subscriptions!
        recently_billed = where("next_billing_date <= '#{Time.now}'")
        recently_billed.each do |account|
          account.subscription_status = account.subscription.status
          account.next_billing_date = account.subscription.next_billing_date
          account.save!
          if account.past_due?
            BillingMailer.problem(account, account.subscription.transactions.last).deliver!
          else
            BillingMailer.receipt(account, account.subscription.transactions.last).deliver!
          end
        end
      end
    end
  end
end