# frozen_string_literal: true

require_relative "helper"
require "currency"

describe Currency do
  before do
    Rate.dataset.delete
    Rate.multi_insert([
      { provider: "ECB", date: Date.today, base: "EUR", quote: "USD", rate: 1.1 },
      { provider: "ECB", date: Date.today, base: "EUR", quote: "GBP", rate: 0.85 },
      { provider: "BOC", date: Date.today, base: "CAD", quote: "USD", rate: 0.74 },
      { provider: "ECB", date: Date.today - 365, base: "EUR", quote: "SEK", rate: 11.0 },
    ])

    db = Sequel::Model.db
    db[:currencies].delete
    db.run(<<~SQL)
      INSERT OR REPLACE INTO currencies (iso_code, start_date, end_date)
      SELECT iso_code, MIN(start_date), MAX(end_date)
      FROM (
        SELECT quote AS iso_code, MIN(date) AS start_date, MAX(date) AS end_date
        FROM rates GROUP BY quote
        UNION ALL
        SELECT base AS iso_code, MIN(date) AS start_date, MAX(date) AS end_date
        FROM rates GROUP BY base
      )
      GROUP BY iso_code
      ORDER BY iso_code
    SQL

    db[:currency_coverages].delete
    db.run(<<~SQL)
      INSERT OR REPLACE INTO currency_coverages (provider_key, iso_code, start_date, end_date)
      SELECT provider, iso_code, MIN(date), MAX(date)
      FROM (
        SELECT provider, quote AS iso_code, date FROM rates
        UNION ALL
        SELECT provider, base AS iso_code, date FROM rates
      )
      GROUP BY provider, iso_code
      ORDER BY provider, iso_code
    SQL
  end

  it "lists all currencies" do
    codes = Currency.all.map(&:iso_code).sort

    _(codes).must_include("USD")
    _(codes).must_include("EUR")
    _(codes).must_include("CAD")
  end

  it "merges date ranges across quote and base" do
    usd = Currency.find("USD")

    _(usd).wont_be_nil
    _(usd.start_date.to_s).must_equal(Date.today.to_s)
    _(usd.end_date.to_s).must_equal(Date.today.to_s)
  end

  it "includes base currencies" do
    eur = Currency.find("EUR")

    _(eur).wont_be_nil
  end

  it "filters active currencies" do
    active_codes = Currency.active.map(&:iso_code)

    _(active_codes).must_include("USD")
    _(active_codes).wont_include("SEK")
  end

  it "returns nil for unknown currency" do
    _(Currency.find("XYZ")).must_be_nil
  end

  it "formats to hash" do
    usd = Currency.find("USD")

    _(usd.to_h[:name]).must_equal("United States Dollar")
    _(usd.to_h[:symbol]).must_equal("$")
    _(usd.to_h[:iso_numeric]).must_equal("840")
  end

  it "includes providers" do
    usd = Currency.find("USD")

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

  it "is case insensitive" do
    _(Currency.find("usd")).wont_be_nil
  end

  it "filters by providers" do
    codes = Currency.with_providers(["ECB"]).map(&:iso_code)

    _(codes).must_include("USD")
    _(codes).must_include("EUR")
    _(codes).wont_include("CAD")
  end

  it "excludes pegged currencies when filtering by providers" do
    codes = Currency.with_providers(["ECB"]).map(&:iso_code)

    _(codes).wont_include("BMD")
  end

  it "includes pegged currencies in the list" do
    codes = Currency.all.map(&:iso_code)

    _(codes).must_include("BMD")
    _(codes).must_include("FKP")
  end

  it "derives pegged currency date range from anchor" do
    bmd = Currency.find("BMD")

    _(bmd).wont_be_nil
    usd = Currency.find("USD")

    _(bmd.end_date.to_s).must_equal(usd.end_date.to_s)
  end

  it "uses later of peg since and anchor start_date" do
    bmd = Currency.find("BMD")
    usd = Currency.find("USD")

    _(bmd.start_date.to_s).must_be(:>=, usd.start_date.to_s)
  end

  it "extends start_date back to peg start when provider data is newer" do
    # AED is pegged to USD since 1997-11-02. Insert provider data starting
    # much later, but anchor (USD) data going back further.
    db = Sequel::Model.db
    Rate.multi_insert([
      { provider: "ECB", date: "1990-01-02", base: "EUR", quote: "USD", rate: 1.0 },
      { provider: "TCMB", date: Date.today, base: "USD", quote: "AED", rate: 3.6725 },
    ])
    db[:currencies].insert_conflict(:replace).insert(iso_code: "USD", start_date: "1990-01-02", end_date: Date.today.to_s)
    db[:currencies].insert_conflict(:replace).insert(iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)
    db[:currency_coverages].insert_conflict(:replace).insert(provider_key: "TCMB", iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)

    aed = Currency.find("AED")

    _(aed.start_date.to_s).must_equal("1997-11-02")
    _(aed.peg).wont_be_nil
    _(aed.providers).must_include("TCMB")
  end

  it "extends start_date in currency list for pegged currencies with provider data" do
    db = Sequel::Model.db
    Rate.multi_insert([
      { provider: "ECB", date: "1990-01-02", base: "EUR", quote: "USD", rate: 1.0 },
      { provider: "TCMB", date: Date.today, base: "USD", quote: "AED", rate: 3.6725 },
    ])
    db[:currencies].insert_conflict(:replace).insert(iso_code: "USD", start_date: "1990-01-02", end_date: Date.today.to_s)
    db[:currencies].insert_conflict(:replace).insert(iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)
    db[:currency_coverages].insert_conflict(:replace).insert(provider_key: "TCMB", iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)

    aed = Currency.all.find { |c| c.iso_code == "AED" }

    _(aed.start_date.to_s).must_equal("1997-11-02")
  end

  it "extends end_date for pegged currency with stale provider data" do
    db = Sequel::Model.db
    # ANG is pegged to USD. Insert a stale ANG row (end_date in the past)
    # but USD is current. The peg should extend ANG's end_date to match USD.
    db[:currencies].insert_conflict(:replace).insert(iso_code: "ANG", start_date: "1999-01-04", end_date: "2025-03-28")
    db[:currency_coverages].insert_conflict(:replace).insert(provider_key: "BDI", iso_code: "ANG", start_date: "1999-01-04", end_date: "2025-03-28")

    usd = Currency.find("USD")

    # find
    ang = Currency.find("ANG")

    _(ang.end_date.to_s).must_equal(usd.end_date.to_s)

    # all (scope=all)
    ang_all = Currency.all.find { |c| c.iso_code == "ANG" }

    _(ang_all.end_date.to_s).must_equal(usd.end_date.to_s)

    # active
    ang_active = Currency.active.find { |c| c.iso_code == "ANG" }

    _(ang_active.end_date.to_s).must_equal(usd.end_date.to_s)
  end

  it "formats pegged currency to hash" do
    bmd = Currency.find("BMD")

    _(bmd.to_h[:iso_code]).must_equal("BMD")
    _(bmd.to_h[:name]).must_equal("Bermudian Dollar")
    _(bmd.to_h[:start_date]).wont_be_nil
    _(bmd.to_h[:end_date]).wont_be_nil
  end

  it "returns peg metadata for pegged currencies" do
    h = Currency.find("BMD").to_h_with_providers

    _(h[:peg][:base]).must_equal("USD")
    _(h[:peg][:rate]).must_equal(1.0)
    _(h[:peg][:authority]).must_equal("Bermuda Monetary Authority")
  end

  it "returns providers for pegged currencies" do
    h = Currency.find("BMD").to_h_with_providers

    _(h[:providers]).must_be_kind_of(Array)
  end

  it "returns providers for non-pegged currencies" do
    h = Currency.find("USD").to_h_with_providers

    _(h[:providers]).must_be_kind_of(Array)
  end

  it "does not return peg metadata for non-pegged currencies" do
    h = Currency.find("USD").to_h_with_providers

    _(h).wont_include(:peg)
  end
end
