defmodule Growth.Indicators.Download do
  @moduledoc """
  To calculate z-scores for the different growth measurements, the system must:

  1. Fetch indicators from World Health Organization
  2. Extract data from excel sheets
  3. Convert the data into proper format, specially, handle with decimal values
  3. Add metadata to make search for the parameters possible

  The following indicators to construct z-scores are fetched:

  * [height for age 0 to 5 years](https://www.who.int/tools/child-growth-standards/standards/length-height-for-age)
  * [height for age 5 to 19 years](https://www.who.int/tools/growth-reference-data-for-5to19-years/indicators/height-for-age)
  * [weight for age 0 to 5 years](https://www.who.int/tools/child-growth-standards/standards/weight-for-age)
  * [weight for age 5 to 10 years](https://www.who.int/tools/growth-reference-data-for-5to19-years/indicators/weight-for-age-5to10-years)
  * [weight for height](https://www.who.int/tools/child-growth-standards/standards/weight-for-length-height)
  * [body mass index (bmi) for age 0 to 5 years](https://www.who.int/toolkits/child-growth-standards/standards/body-mass-index-for-age-bmi-for-age)
  * [body mass index (bmi) for age 5 to 19 years](https://www.who.int/tools/growth-reference-data-for-5to19-years/indicators/bmi-for-age)
  * [head circumference for age 0 to 5 years](https://www.who.int/tools/child-growth-standards/standards/head-circumference-for-age)
  * [arm circumference for age 3 months to 5 years](https://www.who.int/tools/child-growth-standards/standards/arm-circumference-for-age)
  * [subscapular skinfold for age 3 months to 5 years](https://www.who.int/tools/child-growth-standards/standards/subscapular-skinfold-for-age)
  * [triceps skinfold for age 3 months to 5 years](https://www.who.int/tools/child-growth-standards/standards/triceps-skinfold-for-age)
  """

  NimbleCSV.define(IndicatorParser, separator: ",", escape: "\"")

  @urls %{
    height_for_age: %{
      female: %{
        age_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-13-weeks_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_0-to-2-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_girls_2-to-5-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-girls-z-who-2007-exp.xlsx
        ],
        expanded_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/expandable-tables/lhfa-girls-zscore-expanded-tables.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-girls-z-who-2007-exp.xlsx
        ]
      },
      male: %{
        age_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_0-to-13-weeks_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_0-to-2-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/lhfa_boys_2-to-5-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-boys-z-who-2007-exp.xlsx
        ],
        expanded_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/length-height-for-age/expandable-tables/lhfa-boys-zscore-expanded-tables.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/height-for-age-(5-19-years)/hfa-boys-z-who-2007-exp.xlsx
        ]
      }
    },
    weight_for_age: %{
      female: %{
        age_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_girls_0-to-13-weeks_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_girls_0-to-5-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-girls-z-who-2007-exp_7ea58763-36a2-436d-bef0-7fcfbadd2820.xlsx
        ],
        expanded_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/expanded-tables/wfa-girls-zscore-expanded-tables.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-girls-z-who-2007-exp_7ea58763-36a2-436d-bef0-7fcfbadd2820.xlsx
        ]
      },
      male: %{
        age_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_boys_0-to-13-weeks_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/wfa_boys_0-to-5-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-boys-z-who-2007-exp_0ff9c43c-8cc0-4c23-9fc6-81290675e08b.xlsx
        ],
        expanded_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-age/expanded-tables/wfa-boys-zscore-expanded-tables.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/weight-for-age-(5-10-years)/hfa-boys-z-who-2007-exp_0ff9c43c-8cc0-4c23-9fc6-81290675e08b.xlsx
        ]
      }
    },
    weight_for_height: %{
      female: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfl_girls_0-to-2-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfh_girls_2-to-5-years_zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfl-girls-zscore-expanded-table.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfh-girls-zscore-expanded-tables.xlsx
        )
      },
      male: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfl_boys_0-to-2-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/wfh_boys_2-to-5-years_zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfl-boys-zscore-expanded-table.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/weight-for-length-height/expanded-tables/wfh-boys-zscore-expanded-tables.xlsx
        )
      }
    },
    bmi_for_age: %{
      female: %{
        age_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_girls_0-to-13-weeks_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_girls_0-to-2-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_girls_2-to-5-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-girls-z-who-2007-exp.xlsx
        ],
        expanded_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/expanded-tables/bfa-girls-zscore-expanded-tables.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-girls-z-who-2007-exp.xlsx
        ]
      },
      male: %{
        age_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_boys_0-to-13-weeks_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_boys_0-to-2-years_zcores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/bmi_boys_2-to-5-years_zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-boys-z-who-2007-exp.xlsx
        ],
        expanded_tables: ~w[
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/body-mass-index-for-age/expanded-tables/bfa-boys-zscore-expanded-tables.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/growth-reference-5-19-years/bmi-for-age-(5-19-years)/bmi-boys-z-who-2007-exp.xlsx
        ]
      }
    },
    head_circumference_for_age: %{
      female: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-girls-0-13-zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-girls-0-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/expanded-tables/hcfa-girls-zscore-expanded-tables.xlsx
        )
      },
      male: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-boys-0-13-zscores.xlsx
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/hcfa-boys-0-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/head-circumference-for-age/expanded-tables/hcfa-boys-zscore-expanded-tables.xlsx
        )
      }
    },
    arm_circumference_for_age: %{
      female: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/acfa-girls-3-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/expanded-tables/acfa-girls-zscore-expanded-tables.xlsx
        )
      },
      male: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/acfa-boys-3-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/arm-circumference-for-age/expanded-tables/acfa-boys-zscore-expanded-tables.xlsx
        )
      }
    },
    subscapular_skinfold_for_age: %{
      female: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/ssfa-girls-3-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/expanded-tables/ssfa-girls-zscore-expanded-table.xlsx
        )
      },
      male: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/ssfa-boys-3-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/subscapular-skinfold-for-age/expanded-tables/ssfa-boys-zscore-expanded-table.xlsx
        )
      }
    },
    triceps_skinfold_for_age: %{
      female: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/tsfa-girls-3-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/expanded-tables/tsfa-girls-zscore-expanded-tables.xlsx
        )
      },
      male: %{
        age_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/tsfa-boys-3-5-zscores.xlsx
        ),
        expanded_tables: ~w(
          https://cdn.who.int/media/docs/default-source/child-growth/child-growth-standards/indicators/triceps-skinfold-for-age/expanded-tables/tsfa-boys-zscore-expanded-tables.xlsx
        )
      }
    }
  }

  def process_all do
    @urls
    |> Enum.map(&Task.async(__MODULE__, :process_measure, [&1]))
    |> Task.await_many()
  end

  def process_measure({measure, urls}) do
    urls
    |> process_genders()
    |> as_csv()
    |> save(measure)
  end

  def process_genders(%{
        female: %{age_tables: female_urls, expanded_tables: e_female_urls},
        male: %{age_tables: male_urls, expanded_tables: e_male_urls}
      }) do
    [
      {:female, :age, female_urls},
      {:male, :age, male_urls},
      {:female, :expanded, e_female_urls},
      {:male, :expanded, e_male_urls}
    ]
    |> Enum.map(&Task.async(__MODULE__, :process_gender, [&1]))
    |> Task.await_many()
    |> merge()
  end

  def process_gender({gender, category, urls}) do
    urls
    |> Enum.map(&Task.async(__MODULE__, :process, [gender, category, &1]))
    |> Task.await_many()
    |> merge()
  end

  def process(gender, category, url) do
    url
    |> fetch!()
    |> extract!(url)
    |> convert(gender, category, url)
  end

  def fetch!(url) do
    req =
      [url: url]
      |> Keyword.merge(
        :wabanex
        |> Application.get_env(__MODULE__, [])
        |> Keyword.get(:who_req_options, [])
      )
      |> Req.new()

    case Req.get(req) do
      {:ok, %{status: 200, body: body}} ->
        body

      _ ->
        raise("fetch failed for url #{url}")
    end
  end

  def extract!(content, url) do
    with {:ok, package} <- XlsxReader.open(content, source: :binary),
         [sheet_name | _] <- XlsxReader.sheet_names(package),
         {:ok, data} <- XlsxReader.sheet(package, sheet_name) do
      data
    else
      _ -> raise("failed to extract excel for #{url}")
    end
  end

  @common_header ~w(source category gender age_unit age l m s sd3neg sd2neg sd1neg sd0 sd1 sd2 sd3)

  # FIX: (jpd) weight for lenght/height does not have an age in the header row
  def convert([header | rows], gender, category, url) do
    age_unit = header |> hd() |> String.downcase()

    fixed_header = header |> tl() |> Enum.map(&String.downcase/1) |> Enum.map(&String.trim/1)

    parsed_header = ["source" | ["category" | ["gender" | ["age_unit" | ["age" | fixed_header]]]]]

    # NOTE: (jpd): parsing the rows consist in:
    # 1. convert row values to decimal
    # 2. prepend the values url source, gender, and age unit
    # 3. convert row to keyword list using the parsed header
    # 4. convert from keyword list to map
    # 5. fetch common values based on common headers
    # 6. sort row values based on common headers
    parsed_rows =
      rows
      |> Stream.map(fn row -> Enum.map(row, &Decimal.new/1) end)
      |> Stream.map(&[url | [category | [gender | [age_unit | &1]]]])
      |> Stream.map(&Enum.zip(parsed_header, &1))
      |> Stream.map(&Map.new/1)
      |> Stream.map(&Map.take(&1, @common_header))
      |> Enum.map(fn row ->
        Enum.map(@common_header, fn key -> Map.get(row, key) end)
      end)

    [@common_header | parsed_rows]
  end

  def merge(datum) do
    datum
    |> Stream.with_index()
    |> Stream.map(fn
      {data, 0} ->
        data

      {[_ | data], _} ->
        data
    end)
    |> Enum.reduce([], fn data, accum ->
      Enum.concat(accum, data)
    end)
  end

  def as_csv(data) do
    IndicatorParser.dump_to_iodata(data)
  end

  def save(data, measurement) do
    :wabanex
    |> Application.app_dir(["priv", "growth", "indicators", "#{measurement}.csv"])
    |> File.write(data)
  end
end
