require "xeroizer/models/attachment"
require "xeroizer/models/online_invoice"

module Xeroizer
  module Record

    class InvoiceModel < BaseModel
      # To create a new invoice, use the folowing
      # $xero_client.Invoice.build(type: 'ACCREC', ..., contact: {name: 'Foo Bar'},...)
      # However for existing contacts, it is better to reference them by contactid (only)
      # see http://developer.xero.com/documentation/api/contacts/
      # $xero_client.Invoice.build(type: 'ACCREC', ..., contact: {contact_id: 'foo123-bar456-guid'},...)
      # Note that we are not making an api request to xero just to get the contact

      set_permissions :read, :write, :update

      include AttachmentModel::Extensions
      include OnlineInvoiceModel::Extensions

      public

        # Retrieve the PDF version of the invoice matching the `id`.
        # @param [String] id invoice's ID.
        # @param [String] filename optional filename to store the PDF in instead of returning the data.
        def pdf(id, filename = nil)
          pdf_data = @application.http_get(@application.client, "#{url}/#{CGI.escape(id)}", :response => :pdf)
          if filename
            File.open(filename, "wb") { | fp | fp.write pdf_data }
            nil
          else
            pdf_data
          end
        end

    end

    class Invoice < Base

      INVOICE_TYPE = {
        'ACCREC' =>           'Accounts Receivable',
        'ACCPAY' =>           'Accounts Payable'
      } unless defined?(INVOICE_TYPE)
      INVOICE_TYPES = INVOICE_TYPE.keys.sort

      INVOICE_STATUS = {
        'AUTHORISED' =>       'Approved invoices awaiting payment',
        'DELETED' =>          'Draft invoices that are deleted',
        'DRAFT' =>            'Invoices saved as draft or entered via API',
        'PAID' =>             'Invoices approved and fully paid',
        'SUBMITTED' =>        'Invoices entered by an employee awaiting approval',
        'VOIDED' =>           'Approved invoices that are voided'
      } unless defined?(INVOICE_STATUS)
      INVOICE_STATUSES = INVOICE_STATUS.keys.sort

      include Attachment::Extensions
      include OnlineInvoice::Extensions

      set_primary_key :invoice_id
      set_possible_primary_keys :invoice_id, :invoice_number
      list_contains_summary_only true

      guid         :invoice_id
      string       :invoice_number
      string       :reference
      guid         :branding_theme_id
      string       :url
      string       :type
      date         :date
      date         :due_date
      string       :status
      string       :line_amount_types
      decimal      :sub_total, :calculated => true
      decimal      :total_tax, :calculated => true
      decimal      :total, :calculated => true
      decimal      :total_discount
      decimal      :amount_due
      decimal      :amount_paid
      decimal      :amount_credited
      datetime_utc :updated_date_utc, :api_name => 'UpdatedDateUTC'
      string       :currency_code
      decimal      :currency_rate
      datetime     :fully_paid_on_date
      datetime     :expected_payment_date
      datetime     :planned_payment_date
      boolean      :has_attachments

      belongs_to   :contact
      has_many     :line_items, :complete_on_page => true
      has_many     :payments
      has_many     :credit_notes
      has_many     :prepayments

      validates_presence_of :date, :due_date, :unless => :new_record?
      validates_inclusion_of :type, :in => INVOICE_TYPES
      validates_inclusion_of :status, :in => INVOICE_STATUSES, :unless => :new_record?
      validates_inclusion_of :line_amount_types, :in => LINE_AMOUNT_TYPES, :unless => :new_record?
      validates_associated :contact
      validates_associated :line_items, :allow_blanks => true, :unless => :approved?
      validates_associated :line_items, :if => :approved?

      public
        def initialize(parent)
          super(parent)
          @sub_total_is_set = false
          @total_tax_is_set = false
          @total_is_set = false
        end

        # Access the contact name without forcing a download of
        # an incomplete, summary invoice.
        def contact_name
          attributes[:contact] && attributes[:contact][:name]
        end

        # Access the contact ID without forcing a download of an
        # incomplete, summary invoice.
        def contact_id
          attributes[:contact] && attributes[:contact][:contact_id]
        end

        # Helper method to check if the invoice has been approved.
        def approved?
          [ 'AUTHORISED', 'PAID', 'VOIDED' ].include? status
        end

        # Helper method to check if the invoice is accounts payable.
        def accounts_payable?
          type == 'ACCPAY'
        end

        # Helper method to check if the invoice is accounts receivable.
        def accounts_receivable?
          type == 'ACCREC'
        end

        def sub_total=(sub_total)
          @sub_total_is_set = true
          attributes[:sub_total] = sub_total
        end

        def total_tax=(total_tax)
          @total_tax_is_set = true
          attributes[:total_tax] = total_tax
        end

        def total=(total)
          @total_is_set = true
          attributes[:total] = total
        end

        # Calculate sub_total from line_items.
        def sub_total(always_summary = false)
          if !@sub_total_is_set && not_summary_or_loaded_record(always_summary)
            overall_sum = (line_items || []).inject(BigDecimal('0')) { | sum, line_item | sum + line_item.line_amount }

            # If the default amount types are inclusive of 'tax' then remove the tax amount from this sub-total.
            overall_sum -= total_tax if line_amount_types == 'Inclusive'
            overall_sum
          else
            attributes[:sub_total]
          end
        end

        # Calculate total_tax from line_items.
        def total_tax(always_summary = false)
          if !@total_tax_is_set && not_summary_or_loaded_record(always_summary)
            (line_items || []).inject(BigDecimal('0')) { | sum, line_item | sum + line_item.tax_amount }
          else
            attributes[:total_tax]
          end
        end

        # Calculate the total from line_items.
        def total(always_summary = false)
          if !@total_is_set && not_summary_or_loaded_record(always_summary)
            sub_total + total_tax
          else
            attributes[:total]
          end
        end

        def not_summary_or_loaded_record(always_summary)
          !always_summary && loaded_record?
        end

        def loaded_record?
          new_record? ||
            (!new_record? && line_items && line_items.size > 0)
        end

        # Retrieve the PDF version of this invoice.
        # @param [String] filename optional filename to store the PDF in instead of returning the data.
        def pdf(filename = nil)
          parent.pdf(id, filename)
        end

        # Delete an approved invoice with no payments.
        def delete!
          change_status!('DELETED')
        end

        # Void an approved invoice with no payments.
        def void!
          change_status!('VOIDED')
        end

        # Approve a draft invoice
        def approve!
          change_status!('AUTHORISED')
        end

        # Send an email containing the invoice.
        def email
          email_url = "#{parent.url}/#{CGI.escape(id)}/Email"
          parent.application.http_post(parent.application.client, email_url, "")
        end

      protected

        def change_status!(new_status)
          raise CannotChangeInvoiceStatus.new(self, new_status) unless self.payments.size == 0
          self.status = new_status
          self.save
        end

    end

  end
end