# frozen_string_literal: true

require_relative "helper"
require "rate"
require "carry_forward"

describe Rate do
  describe CarryForward, ".apply" do
    it "returns latest available rates on given date" do
      date = Fixtures.latest_date
      rows = Rate.where(date: (date - 14)..date).naked.all
      data = CarryForward.apply(rows, date:)

      _(data.map { |r| r[:date] }.max).must_equal(date)
    end

    it "snaps to nearest prior date when requested date has no rates" do
      sunday = Fixtures.recent_sunday
      friday = Fixtures.preceding_friday(sunday)
      rows = Rate.where(provider: "ECB", date: (sunday - 14)..sunday).naked.all
      data = CarryForward.apply(rows, date: sunday)

      _(data.map { |r| r[:date] }.uniq).must_equal([friday])
    end

    it "includes each provider's most recent date" do
      date = Fixtures.latest_date
      rows = Rate.where(date: (date - 14)..date).naked.all
      data = CarryForward.apply(rows, date:)
      providers = data.map { |r| r[:provider] }.uniq.sort

      _(providers).must_include("ECB")
      _(providers).must_include("BOC")
    end

    it "excludes providers more than 14 days behind the target" do
      date = Fixtures.latest_date
      Rate.dataset.insert(date: date - 20, base: "EUR", quote: "XTS", rate: 1.08, provider: "STALE")

      rows = Rate.where(date: (date - 14)..date).naked.all
      data = CarryForward.apply(rows, date:)
      providers = data.map { |r| r[:provider] }.uniq

      _(providers).must_include("ECB")
      _(providers).wont_include("STALE")
    end

    it "includes providers within 14 days of the target" do
      date = Fixtures.latest_date
      Rate.dataset.insert(date: date - 10, base: "USD", quote: "XTS", rate: 0.92, provider: "FRED")

      rows = Rate.where(date: (date - 14)..date).naked.all
      data = CarryForward.apply(rows, date:)
      providers = data.map { |r| r[:provider] }.uniq

      _(providers).must_include("ECB")
      _(providers).must_include("FRED")
    end

    it "includes rates from different dates within the same provider" do
      date = Fixtures.latest_date
      older_date = date - 3
      Rate.dataset.insert(date: older_date, base: "XTS", quote: "PLN", rate: 0.05, provider: "ECB")

      rows = Rate.where(date: (date - 14)..date).naked.all
      data = CarryForward.apply(rows, date:)
      quotes = data.select { |r| r[:provider] == "ECB" }.map { |r| r[:quote] }

      _(quotes).must_include("PLN")
    end

    it "returns nothing if date predates dataset" do
      date = Date.parse("1901-01-01")
      rows = Rate.where(date: (date - 14)..date).naked.all

      _(CarryForward.apply(rows, date:)).must_be_empty
    end

    it "returns latest rates when client date is ahead of server" do
      future_date = Date.today + 1
      rows = Rate.where(date: (future_date - 14)..future_date).naked.all
      data = CarryForward.apply(rows, date: future_date)

      _(data).wont_be_empty

      today_rows = Rate.where(date: (Date.today - 14)..Date.today).naked.all
      today_data = CarryForward.apply(today_rows, date: Date.today)

      _(data.map { |r| r[:date] }.uniq.sort).must_equal(today_data.map { |r| r[:date] }.uniq.sort)
    end
  end

  describe ".between" do
    it "returns rates between given working dates" do
      start_date = Fixtures.latest_date - 30
      end_date = Fixtures.latest_date
      dates = Rate.between(start_date..end_date).map(:date).sort.uniq

      _(dates.first).must_be(:<=, start_date)
      _(dates.last).must_equal(end_date)
    end

    it "starts on preceding business day if start date is a weekend" do
      sunday = Fixtures.recent_sunday
      monday = sunday + 1
      friday = Fixtures.preceding_friday(sunday)
      dates = Rate.between(sunday..monday).map(:date).uniq

      _(dates).must_include(friday)
    end

    it "returns nothing if end date predates dataset" do
      interval = (Date.parse("1901-01-01")..Date.parse("1901-01-31"))

      _(Rate.between(interval)).must_be_empty
    end

    it "allows start date to predate dataset" do
      start_date = Date.parse("1901-01-01")
      end_date = Fixtures.latest_date
      dates = Rate.between(start_date..end_date).map(:date)

      _(dates).wont_be_empty
    end

    it "returns nothing when start date is in the future" do
      start_date = Date.today + 1
      end_date = start_date + 1
      dates = Rate.between(start_date..end_date).map(:date)

      _(dates).must_be_empty
    end
  end

  describe ".only" do
    it "returns only rate pairs involving the given currencies" do
      currencies = ["CAD", "USD"]
      date = Fixtures.latest_date
      data = Rate.where(date: (date - 14)..date).only(*currencies).all

      data.each do |r|
        _([r.base, r.quote].any? { |c| currencies.include?(c) }).must_equal(true)
      end
    end

    it "includes rates from providers where currency appears as base" do
      date = Fixtures.latest_date
      data = Rate.where(date: (date - 14)..date).only("USD", "EUR").all
      providers = data.map(&:provider).uniq

      _(providers).must_include("BOC")
    end

    it "includes providers whose rates span both sides of the pair" do
      date = Fixtures.latest_date
      data = Rate.where(date: (date - 14)..date).only("JPY", "EUR").all
      providers = data.map(&:provider).uniq

      _(providers).must_include("BOJ")
    end

    it "returns nothing if no matches" do
      _(Rate.only("FOO").all).must_be_empty
    end
  end

  describe ".downsample" do
    let(:interval) { (Fixtures.latest_date - 366)..Fixtures.latest_date }

    it "groups by week" do
      dates = Rate.between(interval).downsample("week")

      _(dates.map(:date).uniq.count).must_be(:<=, 55)
    end

    it "groups by month" do
      dates = Rate.between(interval).downsample("month")

      _(dates.map(:date).uniq.count).must_be(:<=, 14)
    end

    it "sorts by date" do
      dates = Rate.between(interval).downsample("week").map(:date)

      _(dates).must_equal(dates.sort)
    end
  end
end
