class EasyCurrency < ActiveRecord::Base
  include Redmine::SafeAttributes

  has_many :exchange_rates_from, class_name: :EasyCurrencyExchangeRate, foreign_key: :base_code, dependent: :destroy, primary_key: :iso_code
  has_many :exchange_rates_to, class_name: :EasyCurrencyExchangeRate, foreign_key: :to_code, dependent: :destroy, primary_key: :iso_code
  has_many :projects, foreign_key: :easy_currency_code, primary_key: :iso_code, inverse_of: :easy_currency

  validates :name, presence: true
  validates :iso_code, length: {is: 3}, presence: true, uniqueness: true
  validate :validate_max_activated

  safe_attributes 'name', 'iso_code', 'digits_after_decimal_separator', 'symbol','activated', 'project_ids', 'is_default'
  after_create :create_exchange_rates, :ensure_synchronize_task
  before_save :check_default
  after_save :invalidate_currencies_setup
  after_save :invalidate_cache
  after_destroy :invalidate_cache

  attr_protected :id

  ACTIVATED_CURRENCY_LIMIT = 6

  ISO_PATH = File.join EasyExtensions::EASYPROJECT_EASY_PLUGINS_DIR, 'easy_extensions', 'assets', 'xml_data_store', 'iso.xml'

  COUNTRY_MAPPING_ISO_PATH = File.join EasyExtensions::EASYPROJECT_EASY_PLUGINS_DIR, 'easy_extensions', 'assets', 'xml_data_store', 'iso_country_currency_mapping.xml'

  scope :activated, -> {where(activated: true).order(:iso_code)}
  scope :non_activated, -> {where(activated: [false, nil])}

  def self.get_currency_list
    saved = active_currencies_codes
    data = []
    File.open(ISO_PATH) {|f| data = Hash.from_xml(f)['ISO_4217']['CcyTbl']['CcyNtry']}
    data.reject { |x| saved.include?(x['Ccy']) || x['Ccy'].nil? }.uniq { |x| x['Ccy'] }.sort_by { |x| x['Ccy']=~/EUR|USD|CZK|RUB/i ? 0 : 1 }
  end

  def self.get_country_currency_hash(country_code_length = 2)
    return nil unless [2, 3].include? country_code_length
    hash = {}
    File.open(COUNTRY_MAPPING_ISO_PATH) do |file|
      Hash.from_xml(file)["country_currency_mapping"]["countries"]["country"].each {|h| hash[h['country_alphabetic_code_' + country_code_length.to_s]] = h['currency_alphabetic_code']}
    end
    hash
  end

  def self.get_currency_code_for_country_code(country_code)
    country_code = country_code.to_s
    self.get_country_currency_hash(country_code.length).try(:[], country_code)
  end

  def self.active_currencies_codes
    EasyCurrency.pluck(:iso_code)
  end

  def self.get_id_from_string(str)
    EasyCurrency.where(iso_code: str).first.try(:id) || EasyCurrency.where(symbol: str).first.try(:id)
  end

  def create_exchange_rates
    EasyCurrency.where('id <> ?', self.id).each do |currency|
      EasyCurrencyExchangeRate.create(base_code: self.iso_code, to_code: currency.iso_code)
      EasyCurrencyExchangeRate.create(base_code: currency.iso_code, to_code: self.iso_code)
    end
    EasyCurrencyExchangeRate.create(base_code: iso_code, to_code: iso_code, rate: BigDecimal(1))
  end

  def exchange_table(date, currencies = EasyCurrency.all)
    ex_table=[]
    currencies.each do |currency|
      ex_table << EasyCurrencyExchangeRate.find_exchange_rate(self, currency, date)
    end
    ex_table
  end

  def self.exchange_table(date)
    ex_table = {}
    currencies = EasyCurrency.all
    currencies.each do |currency|
      ex_table[currency] = currency.exchange_table(date, currencies)
      return nil if ex_table[currency].any?(&:nil?)
    end
    ex_table
  end

  def self.synchronize_exchange_rates(date, provider = FixerIo)
    if (codes = EasyCurrency.pluck(:iso_code)).any?
      rates = {}
      codes.each do |code|
        response = provider.exchange_table(code, date, codes) || {}
        if response['base'] == code
          response['rates'].each do |target, value|
            record = EasyCurrencyExchangeRate.where(base_code: code, to_code: target, valid_on: date).first_or_initialize
            record.rate = value
            record.save!
          end
          rates[code] = response['rates']
        end
      end
      rates
    end
  end

  def self.exchange_table_by_base(base)
    easy_currency_settings = EasySetting.value(:easy_currency_exchange_rates, nil)
    easy_currency_settings ||= {start: 12, end: 2, day_of_month: Date.today.day}
    months_start = easy_currency_settings['start'].present? ? easy_currency_settings['start'].to_i * -1 : -12
    months_end = easy_currency_settings['end'].present? ? easy_currency_settings['end'].to_i : 2
    day = easy_currency_settings['day_of_month'].present? ? easy_currency_settings['day_of_month'].to_i : Date.today.day
    ex_table = {}
    currencies = EasyCurrency.where('id <> ?', base.id).order(:iso_code)
    day = [1, [Time.days_in_month(Date.today.month), day].min].max
    current_date = Date.today.change(day: day)
    months_end.downto(months_start) do |i|
      row=[]
      currencies.each do |target|
        row << EasyCurrencyExchangeRate.find_exchange_rate(base, target, current_date.advance(months: i))
      end
      ex_table[current_date.advance(months: i)] = row
    end
    ex_table
  end

  def validate_max_activated
    errors.add(:base, l(:limit_for_currencies_hit, limit: ACTIVATED_CURRENCY_LIMIT)) unless EasyCurrency.all.count <= ACTIVATED_CURRENCY_LIMIT
  end

  def invalidate_currencies_setup
    self.class.invalidate_currencies_setup
  end

  def self.invalidate_currencies_setup
    if EasySetting.value('easy_currencies_initialized') && (setting = EasySetting.where(name: 'easy_currencies_initialized', project_id: nil).first)
      setting.value = false
      setting.save
    end
  end

  def invalidate_cache
    Rails.cache.delete [self.class.name, self.iso_code]
    Rails.cache.delete [self.class.name, self.iso_code, 'name']
  end

  def self.get_symbol(iso_code)
    Rails.cache.fetch [self.name, iso_code] do
      EasyCurrency.where(iso_code: iso_code).where.not(symbol: ['', nil]).limit(1).pluck(:symbol).first || iso_code || ''
    end
  end

  def self.get_name(iso_code)
    Rails.cache.fetch [self.name, iso_code, 'name'] do
      EasyCurrency.where(iso_code: iso_code).limit(1).pluck(:name).first.presence || iso_code || ''
    end
  end

  def to_s
    name
  end

  private

  def check_default
    if self.is_default_changed?
      self.class.update_all(is_default: false) if self.is_default?
    end
  end

  def ensure_synchronize_task
    unless EasyRakeTaskSynchronizeCurrencies.exists?
      t = EasyRakeTaskSynchronizeCurrencies.new(active: true, settings: {}, period: :daily, interval: 1, next_run_at: (Date.tomorrow.to_time + 8.hour))
      t.builtin = 1
      t.save!
    end
  end

end
