"""Compare reported positions with normal and high-volatility trading volume observed before the holdings date.

Research example. Live observations can change.
"""

from datetime import UTC, datetime
from decimal import Decimal
from textwrap import fill

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

AS_OF = datetime.now(UTC).date()
NAVY, BLUE, PINK = "#132244", "#8fb9ff", "#e7a2cc"


def fetch(qj):
    return {
        "vix_history": qj.domains.call(
            "macro.indicators.get_fred_data_series_by_id",
            provider="fred",
            search_id="VIXCLS",
            start="2020-01-01",
            end="2025-12-31",
        ),
        "holders": qj.domains.call(
            "regulatory.ownership.get_institutional_holders",
            provider="fmp",
            symbol="AAPL",
            year=2025,
            quarter=3,
            limit=100,
        ),
        "prices": qj.domains.call(
            "equity.pricing.get_historical_prices",
            provider="eod",
            symbols=["AAPL"],
            start_date="2020-01-01",
            end_date="2025-12-31",
            frequency="1d",
        ),
    }


def analyze(raw):
    holders = frame(raw["holders"])
    holders = holders.loc[(holders["sharesType"] == "SH") & (holders["putCallShare"] == "Share")].copy()
    prices = history(raw["prices"], "AAPL")
    vix = macro(raw["vix_history"], "VIXCLS")
    rows = []
    for holder in holders.to_dict("records"):
        volume = prices.loc[prices.index <= holder["date"], "volume"].tail(20)
        if len(volume) < 20:
            continue
        mean_volume = sum(volume, Decimal(0)) / Decimal(len(volume))
        sample = prices.loc[prices.index <= holder["date"], ["volume"]].tail(252)
        sample = sample.join(vix.rename("VIX"), how="inner").dropna()
        stressed = sample.loc[sample["VIX"] >= sample["VIX"].quantile(0.85), "volume"]
        if len(sample) < 200 or stressed.empty:
            continue
        stress_volume = sum(stressed, Decimal(0)) / Decimal(len(stressed))
        rows.append(
            {
                "Stress ADV-equivalent days": ratio(holder["sharesNumber"], stress_volume),
                "CIK": str(holder["cik"]).zfill(10),
                "Holdings date": holder["date"],
                "ADV-equivalent days": ratio(holder["sharesNumber"], mean_volume),
            }
        )
    result = pd.DataFrame(rows)
    if result.empty:
        raise ValueError("No equity holdings with sufficient historical volume")
    result = result.nlargest(10, "ADV-equivalent days").sort_values("ADV-equivalent days")
    fig, (ax,) = canvas("AAPL · institutional position size in context", "SEC CIK")
    y = np.arange(len(result))
    ax.barh(y - 0.18, result["ADV-equivalent days"], 0.36, label="Trailing 20 sessions")
    ax.barh(y + 0.18, result["Stress ADV-equivalent days"], 0.36, label="High-VIX days in prior 252 sessions")
    ax.set_yticks(y, result["CIK"])
    legend(ax)
    ax.set_xlabel("Position shares / mean daily shares traded · days")
    return result, fig


def payload(response):
    if hasattr(response, "to_dict"):
        response = response.to_dict()
    while isinstance(response, dict):
        if response.get("error") or response.get("error_code") or response.get("success") is False:
            raise ValueError("Provider request failed")
        if "data" in response:
            response = response["data"]
        elif "value" in response:
            response = response["value"]
        else:
            break
    return response


def frame(response, date="date"):
    rows = payload(response)
    if not isinstance(rows, list) or not rows:
        raise ValueError("No provider observations returned")
    result = pd.DataFrame(rows)
    if date:
        result[date] = pd.to_datetime(result[date], utc=True)
        result = result.sort_values(date)
    return result


def decimal(value):
    if pd.isna(value):
        raise ValueError("Required monetary value or quantity is missing")
    return Decimal(str(value))


def ratio(numerator, denominator):
    denominator = decimal(denominator)
    if denominator == 0:
        raise ValueError("Required denominator is zero")
    return float(decimal(numerator) / denominator)


def history(response, symbol, adjusted=True):
    values = payload(response)
    if not isinstance(values, dict) or symbol not in values:
        raise ValueError(f"No price coverage for {symbol}")
    result = frame(values[symbol]).set_index("date")
    field = "adjusted_close" if adjusted else "close"
    result["price"] = result[field].map(decimal)
    result["volume"] = result["volume"].map(decimal)
    if result.index.has_duplicates:
        raise ValueError("Duplicate trading dates")
    return result


def macro(response, column):
    values = frame(response).set_index("date")[column]
    values = pd.to_numeric(values, errors="coerce").dropna()
    if values.empty:
        raise ValueError(f"No numeric observations for {column}")
    return values


def canvas(title, ylabel, panels=1):
    plt.rcParams.update(
        {
            "figure.facecolor": NAVY,
            "axes.facecolor": NAVY,
            "savefig.facecolor": NAVY,
            "text.color": "#e9eef8",
            "axes.labelcolor": "#afc1dc",
            "xtick.color": "#afc1dc",
            "ytick.color": "#afc1dc",
            "axes.edgecolor": "#34486b",
            "grid.color": "#34486b",
            "font.size": 10,
            "axes.prop_cycle": plt.cycler(color=[BLUE, PINK, "#ba9dea"]),
        }
    )
    fig, axes = plt.subplots(panels, 1, figsize=(9, 6.1), squeeze=False, layout="constrained")
    fig.suptitle(fill(title, width=58), fontsize=17, x=0.04, ha="left", weight="medium")
    for ax in axes.flat:
        ax.grid(axis="y", alpha=0.45)
        ax.set_axisbelow(True)
        ax.spines[["top", "right"]].set_visible(False)
    axes.flat[0].set_ylabel(ylabel)
    return fig, list(axes.flat)


def legend(ax):
    ax.legend(frameon=False, labelcolor="#e9eef8", fontsize=9)


def run():
    from quantjourney.sdk import QuantJourneyAPI

    qj = QuantJourneyAPI.from_env()
    raw = fetch(qj)
    table, fig = analyze(raw)
    print(table.to_string(index=False))
    fig.savefig(OUTPUT, dpi=180)
    plt.show()


OUTPUT = "sdk_ownership_capacity.png"

if __name__ == "__main__":
    run()
