#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

class Invoice < ActiveRecord::Base
  unloadable
  has_and_belongs_to_many :issues
  belongs_to :customer 
  validates_presence_of :doc_number, :id, :customer_id, :txn_date
  self.primary_key = :id
  
  # Get the quickbooks-ruby base for invoice
  def self.get_base
    Qbo.get_base(:invoice)
  end
  
  # sync ALL the invoices
  def self.sync
    logger.debug "Syncing all invoices"
    last = Qbo.first.last_sync

    query = "SELECT Id, DocNumber FROM Invoice"
    query << " WHERE Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
  
    # TODO actually do something with the above query
    # .all() is never called since count is never initialized
    if count == 0
      invoices = get_base.all 
    else
      invoices = get_base.query()
    end
     
    invoices.each { | invoice | 
      process_invoice invoice
    }
  end
  
  #sync by invoice ID
  def self.sync_by_id(id)
    logger.debug "Syncing invoice #{id}"
    invoice = get_base.fetch_by_id(id) 
    process_invoice invoice
  end
  
  private
  
  # Attach the invoice to the issue
  def self.attach_to_issue(issue, invoice)
    return if issue.nil?
    
    # skip this issue if the issue customer is not the same as the invoice customer
    return if issue.customer_id != invoice.customer_ref.value.to_i
    
    logger.debug "Attaching invoice #{invoice.id} to issue #{issue.id}"
    
    invoice = Invoice.find_or_create_by(id: invoice.id)

    unless issue.invoices.include?(invoice)
      issue.invoices << invoice
      issue.save!
    end
    
    compare_custom_fields(issue, invoice)
  end
  
  # processes the invoice into the database
  def self.process_invoice(invoice)
    logger.info "Processing invoice #{invoice.id}"

    # Load the invoice into the database
    invoice = Invoice.find_or_create_by(id: invoice.id)
    invoice.doc_number = invoice.doc_number 
    invoice.id = invoice.id
    invoice.customer_id = invoice.customer_ref
    invoice.txn_date = invoice.txn_date
    invoice.save!

    # Scan the private notes for hashtags and attach to the applicable issues
    if not invoice.private_note.nil?
      invoice.private_note.scan(/#(\w+)/).flatten.each { |issue|
        attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
      }
    end
    
    # Scan the line items for hashtags and attach to the applicable issues
    invoice.line_items.each { |line|
      if line.description
        line.description.scan(/#(\w+)/).flatten.each { |issue|
          attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
        }
      end
    }
  end
  
  # compares the custome fields on invoices & issues and updates the invoice as needed
  #
  # the issue here is when two or more issues share an invoice with the same custom field, but diffrent values
  # this condions causes an infinite loop as the webhook is called when an invoice is updated
  # TODO maybe add a cf_sync_confict flag to invoices
  def self.compare_custom_fields(issue, invoice)
    logger.debug "Comparing custom fields"
    # TODO break if Invoice.find(invoice.id).cf_sync_confict
    is_changed = false
    
    # update the invoive custom fields with infomation from the issue if available
    invoice.custom_fields.each { |cf|
      
      # VIN from the attached vehicle
      # TODO move this into seperate plugin
      # TODO create hook for seperate plugin 
      begin
        if cf.name.eql? "VIN"
          # Only update if blank to prevent infite loops
          # TODO check cf_sync_confict flag once implemented
          if cf.string_value.to_s.blank?
            logger.debug " VIN was blank, updating the invoice vin in quickbooks"
            vin = Vehicle.find(issue.vehicles_id).vin
            break if vin.nil?
            if not cf.string_value.to_s.eql? vin
              cf.string_value = vin.to_s
              logger.debug "VIN has changed"
              is_changed = true
            end

          end
        end
      rescue
        #do nothing
      end
      
      # Custom Values
      begin
        value = issue.custom_values.find_by(custom_field_id: CustomField.find_by_name(cf.name).id)
        
        # Check to see if the value is blank...
        if not value.value.to_s.blank?
          # Check to see if the value is diffrent
          if not cf.string_value.to_s.eql? value.value.to_s
            # update the custom field on the invoice
            cf.string_value = value.value.to_s
            is_changed = true
          end
        end
      rescue
        # Nothing to do here, there is no match
      end
    }

    # Push updates
    begin
      logger.debug "Trying to update invoice"
      get_base.update(invoice) if is_changed
    rescue
      # Do nothing, probaly custome field sync confict on the invoice. 
      # This is a problem with how it's billed
      # TODO Add notes in memo area
      # TODO flag Invoice.cf_sync_confict here
      logger.error "Failed to update invoice"
    end
  end

  # Magic Method
  # Maps Get/Set methods to QBO invoice object
  def method_missing(sym, *arguments)
    # Check to see if the method exists
    if Quickbooks::Model::Invoice.method_defined?(sym)
      # download details if required
      pull unless @details
      method_name = sym.to_s
      # Setter
      if method_name[-1, 1] == "="
        @details.method(method_name).call(arguments[0])
      # Getter
      else
        return @details.method(method_name).call 
      end
    end
  end
  
  # pull the details from quickbooks
  def pull
    begin
      raise Exception unless self.id
      @details = Qbo.get_base(:invoice).fetch_by_id(self.id)
    rescue Exception => e
      @details = Quickbooks::Model::Invoice.new
    end
  end
    
end
