# frozen_string_literal: true

require "currency_coverage"
require "money/currency"
require "peg"

# Model backed by the materialized currencies table. Populated incrementally
# during Provider#backfill. Derives one row per currency with date range.
class Currency < Sequel::Model(:currencies)
  unrestrict_primary_key

  one_to_many :currency_coverages, key: :iso_code
  many_to_many :providers, join_table: :currency_coverages, left_key: :iso_code, right_key: :provider_key

  dataset_module do
    def active
      cutoff = (Date.today - 30).to_s
      where { end_date >= cutoff }
    end

    def with_providers(keys)
      iso_codes = CurrencyCoverage.where(provider_key: keys)
        .select(:iso_code).distinct
      where(iso_code: iso_codes)
    end
  end

  class << self
    def all
      merge_pegged(super)
    end

    def active
      merge_pegged(super.all)
    end

    def map(&block)
      all.map(&block)
    end

    def find(code)
      code = code.upcase
      peg = Peg.find(code)
      db_record = where(iso_code: code).first

      if db_record
        apply_peg(db_record, peg, where(iso_code: peg.base).first) if peg
        db_record
      elsif peg
        anchor = where(iso_code: peg.base).first
        return unless anchor

        new_pegged(peg, anchor)
      end
    end

    private

    def merge_pegged(db_currencies)
      anchors = db_currencies.to_h { |c| [c.iso_code, c] }

      pegged = Peg.all.filter_map do |peg|
        existing = anchors[peg.quote]
        if existing
          apply_peg(existing, peg, anchors[peg.base])
          nil
        else
          anchor = anchors[peg.base]
          next unless anchor

          new_pegged(peg, anchor)
        end
      end

      (db_currencies + pegged).sort_by(&:iso_code)
    end

    def apply_peg(record, peg, anchor)
      record.instance_variable_set(:@peg, peg)
      return unless anchor

      peg_start = [peg.since, Date.parse(anchor.start_date.to_s)].compact.max.to_s
      record.start_date = peg_start if peg_start < record.start_date.to_s
      record.end_date = anchor.end_date if anchor.end_date.to_s > record.end_date.to_s
    end

    def new_pegged(peg, anchor)
      c = new(iso_code: peg.quote, start_date: anchor.start_date, end_date: anchor.end_date)
      apply_peg(c, peg, anchor)
      c
    end
  end

  attr_reader :peg

  def metadata
    @metadata ||= Money::Currency.find(iso_code)
  end

  def to_h
    {
      iso_code: iso_code,
      iso_numeric: metadata&.iso_numeric,
      name: metadata&.name || iso_code,
      symbol: metadata&.symbol,
      start_date: start_date.to_s,
      end_date: end_date.to_s,
    }
  end

  def providers
    CurrencyCoverage.where(iso_code: iso_code)
      .order(:provider_key)
      .select_map(:provider_key)
  end

  def to_h_with_providers
    h = to_h.merge(providers: providers)
    h[:peg] = { base: peg.base, rate: peg.rate, authority: peg.authority } if peg
    h
  end
end
