# frozen_string_literal: true

require "net/http"

require "provider/adapters/adapter"

class Provider
  module Adapters
    # International Monetary Fund representative exchange rates.
    # Daily rates for 50+ currencies. Most rates are foreign currency per USD;
    # currencies marked with (1) are USD per foreign unit.
    class IMF < Adapter
      BASE_URL = "https://www.imf.org/external/np/fin/data/rms_mth.aspx"

      # Currency name to ISO code mapping
      CURRENCY_MAP = {
        "afghan afghani" => "AFN",
        "algerian dinar" => "DZD",
        "angolan kwanza" => "AOA",
        "australian dollar" => "AUD",
        "bahamian dollar" => "BSD",
        "bahrain dinar" => "BHD",
        "bangladeshi taka" => "BDT",
        "barbados dollar" => "BBD",
        "botswana pula" => "BWP",
        "brazilian real" => "BRL",
        "brunei dollar" => "BND",
        "canadian dollar" => "CAD",
        "chilean peso" => "CLP",
        "chinese yuan" => "CNY",
        "colombian peso" => "COP",
        "costa rican colon" => "CRC",
        "czech koruna" => "CZK",
        "danish krone" => "DKK",
        "djibouti franc" => "DJF",
        "egyptian pound" => "EGP",
        "euro" => "EUR",
        "fiji dollar" => "FJD",
        "hungarian forint" => "HUF",
        "icelandic krona" => "ISK",
        "indian rupee" => "INR",
        "iranian rial" => "IRR",
        "israeli new shekel" => "ILS",
        "japanese yen" => "JPY",
        "jordanian dinar" => "JOD",
        "kazakhstani tenge" => "KZT",
        "kuwaiti dinar" => "KWD",
        "libyan dinar" => "LYD",
        "malaysian ringgit" => "MYR",
        "mauritian rupee" => "MUR",
        "mexican peso" => "MXN",
        "moldovan leu" => "MDL",
        "mongolian tugrik" => "MNT",
        "moroccan dirham" => "MAD",
        "mozambican metical" => "MZN",
        "myanmar kyat" => "MMK",
        "namibian dollar" => "NAD",
        "nepalese rupee" => "NPR",
        "new zealand dollar" => "NZD",
        "nigerian naira" => "NGN",
        "norwegian krone" => "NOK",
        "omani rial" => "OMR",
        "pakistani rupee" => "PKR",
        "peruvian sol" => "PEN",
        "philippine peso" => "PHP",
        "polish zloty" => "PLN",
        "qatari riyal" => "QAR",
        "romanian leu" => "RON",
        "russian ruble" => "RUB",
        "rwandan franc" => "RWF",
        "samoan tala" => "WST",
        "saudi arabian riyal" => "SAR",
        "singapore dollar" => "SGD",
        "south african rand" => "ZAR",
        "korean won" => "KRW",
        "sri lankan rupee" => "LKR",
        "swedish krona" => "SEK",
        "swiss franc" => "CHF",
        "thai baht" => "THB",
        "trinidadian dollar" => "TTD",
        "tunisian dinar" => "TND",
        "ugandan shilling" => "UGX",
        "u.a.e. dirham" => "AED",
        "u.k. pound" => "GBP",
        "u.s. dollar" => "USD",
        "uruguayan peso" => "UYU",
        "venezuelan bolivar" => "VES",
        "yemeni rial" => "YER",
        "zambian kwacha" => "ZMW",
      }.freeze

      def fetch(after: nil, upto: nil)
        end_date = upto || Date.today

        dataset = []
        cursor = Date.new(after.year, after.month, 1)

        while cursor <= end_date
          last_day = Date.new(cursor.year, cursor.month, -1)
          dataset.concat(parse(fetch_month(last_day, "REP")))
          dataset.concat(parse_sdrcv(fetch_month(last_day, "SDRCV")))
          cursor >>= 1
        end

        dataset = dataset.select { |r| r[:date] >= after } if after
        dataset
      end

      def parse(tsv)
        parse_rows(tsv) do |currency_name, rate, date|
          indirect = currency_name.end_with?("(1)")
          clean_name = currency_name.delete_suffix("(1)").strip.downcase
          iso = CURRENCY_MAP[clean_name]
          next unless iso
          next if iso == "USD"

          if indirect
            { date:, base: iso, quote: "USD", rate: }
          else
            { date:, base: "USD", quote: iso, rate: }
          end
        end
      end

      # SDR cross rates: "SDRs per Currency unit" — only emit USD/XDR. Other pairs
      # in this report are derived by IMF from the rep series and would create
      # ambiguous bridges in BaseConversion if retained.
      def parse_sdrcv(tsv)
        parse_rows(tsv) do |currency_name, rate, date|
          iso = CURRENCY_MAP[currency_name.downcase]
          next unless iso == "USD"

          { date:, base: iso, quote: "XDR", rate: }
        end
      end

      private

      def parse_rows(tsv)
        return [] if tsv.nil? || tsv.strip.empty?

        records = []
        dates = nil

        tsv.each_line(chomp: true) do |line|
          next if line.strip.empty?

          if line.start_with?("Currency\t")
            headers = line.split("\t")
            dates = headers[1..].map { |h| Date.parse(h.strip) }
            next
          end

          next unless dates

          cols = line.split("\t")
          currency_name = cols[0]&.strip
          next unless currency_name

          cols[1..].zip(dates).each do |value, date|
            next unless date

            cleaned = value&.tr(",", "")&.gsub(/[^0-9.\-]/, "")
            next if cleaned.nil? || cleaned.empty?

            rate = Float(cleaned)
            next if rate.zero?

            record = yield(currency_name, rate, date)
            records << record if record
          end
        end

        records
      end

      def fetch_month(last_day, report_type)
        url = URI(BASE_URL)
        url.query = URI.encode_www_form(
          SelectDate: last_day.to_s,
          reportType: report_type,
          tsvflag: "Y",
        )

        Net::HTTP.get(url)
      end
    end
  end
end
