"""Reproduce the price analysis in /guides/most-expensive-elements.

1. Annual average prices of the five traded platinum-group metals and gold, 2019 to 2026.
   Rhodium, iridium, ruthenium, platinum and palladium: Johnson Matthey daily base prices
   (London 09:00, USD per troy ounce).
   Gold: World Gold Council daily gold price series (LBMA Gold Price PM, USD per troy ounce).
   Each annual value is the arithmetic mean of the daily prices published that year.
   2026 runs from January 1 to the end date below.

2. DOE isotope prices restated in August 2026 dollars with the US CPI-U
   (BLS series CUUR0000SA0, not seasonally adjusted). The June 2000 price list is
   deflated with the June 2000 index; earlier californium-252 prices, which are
   known only by year, use annual averages.

Requires Python 3.11+ and network access. Writes two CSV files next to this script.
"""

import csv
import datetime as dt
import http.cookiejar
import json
import statistics
import urllib.parse
import urllib.request
from pathlib import Path

OUT = Path(__file__).parent
YEARS = range(2019, 2027)
END_2026 = dt.date(2026, 10, 6)
UA = {"User-Agent": "Mozilla/5.0"}
JM_METALS = {"Rh": "rhodium", "Ir": "iridium", "Ru": "ruthenium", "Pt": "platinum", "Pd": "palladium"}

JM_URL = "https://matthey.com/products-and-markets/pgms-and-circularity/pgm-management"
JM_PORTLET = "_jm_metal_price_portlet_JmMetalPricePortlet_"
JM_QUERY = (
    "?p_p_id=jm_metal_price_portlet_JmMetalPricePortlet&p_p_lifecycle=2"
    "&p_p_state=normal&p_p_mode=view&p_p_cacheability=cacheLevelPage"
)
WGC_URL = "https://fsapi.gold.org/api/goldprice/v13/chart/price/usd/oz/{start},{end}"
BLS_URL = "https://api.bls.gov/publicAPI/v2/timeseries/data/"

CPI_TARGET = ("2026", "M08")  # August 2026, latest index at the time of writing

# (isotope, price year, CPI period, published price, unit, source)
DOE_PRICES = [
    ("Californium-252", "1987", "M13", 27.00, "microgram", "Martin et al. (1999)"),
    ("Californium-252", "1989", "M13", 50.00, "microgram", "Martin et al. (1999)"),
    ("Californium-252", "1994", "M13", 55.00, "microgram", "Martin et al. (1999)"),
    ("Californium-252", "1998", "M13", 56.00, "microgram", "Martin et al. (1999)"),
    ("Californium-252", "2000", "M06", 60.00, "microgram", "ORNL price list, June 2000"),
    ("Berkelium-249", "2000", "M06", 185.65, "microgram", "ORNL price list, June 2000"),
    ("Curium-244", "2000", "M06", 185.65, "milligram", "ORNL price list, June 2000"),
    ("Plutonium-239", "2000", "M06", 4.80, "milligram", "ORNL price list, June 2000"),
    ("Americium-241", "2000", "M06", 750.00, "gram", "ORNL price list, June 2000"),
    ("Technetium-99", "2000", "M06", 83.85, "gram", "ORNL price list, June 2000"),
]
PER_GRAM = {"microgram": 1_000_000, "milligram": 1_000, "gram": 1}


def jm_daily(opener, metal, year):
    end = min(dt.date(year, 12, 31), END_2026)
    form = urllib.parse.urlencode({
        f"{JM_PORTLET}selectedMetal0": metal,
        f"{JM_PORTLET}start_Date": f"01-01-{year}",
        f"{JM_PORTLET}end_Date": end.strftime("%d-%m-%Y"),
    }).encode()
    req = urllib.request.Request(JM_URL + JM_QUERY, data=form, headers=UA)
    rows = json.load(opener.open(req))["metalList"]
    return {
        dt.datetime.strptime(r["metalValueDate"], "%d/%m/%Y").date(): float(r["price"])
        for r in rows
    }


def find_pairs(node):
    if isinstance(node, list) and node and isinstance(node[0], list) and len(node[0]) == 2:
        return node
    children = node.values() if isinstance(node, dict) else node if isinstance(node, list) else []
    for child in children:
        found = find_pairs(child)
        if found:
            return found
    return None


def wgc_daily(year):
    start = dt.datetime(year, 1, 1, tzinfo=dt.UTC)
    end = dt.datetime.combine(min(dt.date(year, 12, 31), END_2026), dt.time(23), tzinfo=dt.UTC)
    url = WGC_URL.format(start=int(start.timestamp() * 1000), end=int(end.timestamp() * 1000))
    data = json.load(urllib.request.urlopen(urllib.request.Request(url, headers=UA)))
    return {
        dt.datetime.fromtimestamp(ms / 1000, dt.UTC).date(): float(price)
        for ms, price in find_pairs(data)
        if price is not None
    }


def cpi(periods):
    values = {}
    # One request per year stays within the keyless BLS API's range limits.
    for year in sorted({year for year, _ in periods}):
        body = json.dumps({
            "seriesid": ["CUUR0000SA0"],
            "startyear": year,
            "endyear": year,
            "annualaverage": True,
        }).encode()
        req = urllib.request.Request(BLS_URL, data=body, headers={**UA, "Content-Type": "application/json"})
        for row in json.load(urllib.request.urlopen(req))["Results"]["series"][0]["data"]:
            values[(row["year"], row["period"])] = float(row["value"])
    return {period: values[period] for period in periods}


def main():
    opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
    opener.addheaders = list(UA.items())
    opener.open(JM_URL).read()  # session cookie for the price portlet

    with open(OUT / "precious-metal-annual-averages.csv", "w", newline="") as f:
        writer = csv.writer(f)
        header = ["year", "period_end"]
        for name in [*JM_METALS.values(), "gold"]:
            header += [f"{name}_usd_per_troy_oz", f"{name}_days"]
        writer.writerow([*header, "rhodium_to_gold_ratio"])
        for year in YEARS:
            series = {name: jm_daily(opener, code, year) for code, name in JM_METALS.items()}
            series["gold"] = wgc_daily(year)
            means = {name: statistics.fmean(daily.values()) for name, daily in series.items()}
            row = [year, min(dt.date(year, 12, 31), END_2026).isoformat()]
            for name, daily in series.items():
                row += [round(means[name], 2), len(daily)]
            writer.writerow([*row, round(means["rhodium"] / means["gold"], 2)])

    periods = {(year, period) for _, year, period, *_ in DOE_PRICES} | {CPI_TARGET}
    index = cpi(periods)
    with open(OUT / "doe-isotope-prices-inflation-adjusted.csv", "w", newline="") as f:
        writer = csv.writer(f)
        writer.writerow([
            "isotope", "price_year", "cpi_period", "published_price_usd", "unit",
            "published_usd_per_gram", "cpi_u_then", "cpi_u_aug_2026",
            "aug_2026_usd_per_gram", "source",
        ])
        for isotope, year, period, price, unit, source in DOE_PRICES:
            per_gram = price * PER_GRAM[unit]
            factor = index[CPI_TARGET] / index[(year, period)]
            writer.writerow([
                isotope, year, "annual average" if period == "M13" else "June",
                price, unit, round(per_gram, 2), index[(year, period)], index[CPI_TARGET],
                round(per_gram * factor), source,
            ])


if __name__ == "__main__":
    main()
