# frozen_string_literal: true

require "net/http"
require "ox"

require "provider/adapters/adapter"

class Provider
  module Adapters
    # Bank of Russia. Publishes daily rates for ~54 currencies against RUB,
    # plus daily reference prices for gold, silver, platinum and palladium.
    # FX uses XML_daily for the currency list and XML_dynamic for date ranges.
    # Metals come from xml_metall in RUB per gram; values are normalized to
    # per troy ounce here.
    class CBR < Adapter
      DAILY_URL = URI("https://www.cbr.ru/scripts/XML_daily.asp")
      DYNAMIC_URL = URI("https://www.cbr.ru/scripts/XML_dynamic.asp")
      METAL_URL = URI("https://www.cbr.ru/scripts/xml_metall.asp")

      METAL_CODES = {
        "1" => "XAU",
        "2" => "XAG",
        "3" => "XPT",
        "4" => "XPD",
      }.freeze

      def fetch(after:, upto: nil)
        end_date = upto || Date.today
        currencies = fetch_currency_list
        fx = currencies.flat_map { |id, code| fetch_dynamic(id, code, after, end_date) }
        fx + fetch_metals(after, end_date)
      end

      def parse_metals(xml)
        Ox.load(xml).locate("Metall/Record").filter_map do |row|
          base = METAL_CODES[row[:Code]]
          next unless base

          date = Date.strptime(row[:Date], "%d.%m.%Y")
          next if date.saturday? || date.sunday?

          buy = row.locate("Buy").first&.text
          next unless buy && !buy.empty?

          rate = Float(buy.tr(",", "."), exception: false)
          next unless rate&.positive?

          { date:, base:, quote: "RUB", rate: rate * GRAMS_PER_TROY_OUNCE }
        end
      end

      private

      def fetch_metals(start_date, end_date)
        url = METAL_URL.dup
        url.query = URI.encode_www_form(
          date_req1: start_date.strftime("%d/%m/%Y"),
          date_req2: end_date.strftime("%d/%m/%Y"),
        )

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

      def fetch_currency_list
        doc = Ox.load(Net::HTTP.get(DAILY_URL))
        doc.locate("ValCurs/Valute").filter_map do |v|
          code = v.locate("CharCode").first&.text
          next unless code && !code.empty?

          [v[:ID], code]
        end
      end

      def fetch_dynamic(valute_id, code, start_date, end_date)
        url = DYNAMIC_URL.dup
        url.query = URI.encode_www_form(
          date_req1: start_date.strftime("%d/%m/%Y"),
          date_req2: end_date.strftime("%d/%m/%Y"),
          VAL_NM_RQ: valute_id,
        )

        Ox.load(Net::HTTP.get(url)).locate("ValCurs/Record").filter_map do |row|
          date = Date.strptime(row[:Date], "%d.%m.%Y")
          next if date.saturday? || date.sunday?

          rate = extract_rate(row)
          next unless rate

          { date:, base: code, quote: "RUB", rate: }
        end
      end

      def extract_rate(node)
        vunit = node.locate("VunitRate").first
        return Float(vunit.text.tr(",", ".")) if vunit&.text && !vunit.text.empty?

        value = node.locate("Value").first
        return unless value&.text

        nominal = node.locate("Nominal").first&.text.to_i
        Float(value.text.tr(",", ".")) / nominal
      end
    end
  end
end
