from __future__ import annotations

import hashlib
import json
from pathlib import Path

import numpy as np
import pandas as pd


NON_FEATURE_COLUMNS = {
    "symbol", "date", "sector", "market_regime",
    "open", "high", "low", "close", "adjusted_close", "volume",
    "adjusted_open", "adjusted_high", "adjusted_low", "adjusted_volume",
    "adjustment_factor", "is_adjusted",
    "trades", "value_traded", "market_cap",
    "provider", "provider_trust", "source_symbol", "fetched_at", "source_reference",
    "raw_artifact", "adjustment_source", "adjustment_method",
}


def feature_columns(frame: pd.DataFrame) -> list[str]:
    columns: list[str] = []
    for column in frame.select_dtypes(include=[np.number, "boolean"]).columns:
        if column in NON_FEATURE_COLUMNS:
            continue
        if column.startswith(("label_", "forward_return_", "entry_open_", "exit_close_", "benchmark_forward_return_")):
            continue
        columns.append(column)
    return sorted(columns)


def frame_fingerprint(frame: pd.DataFrame, columns: list[str] | None = None) -> str:
    selected = frame[columns] if columns else frame
    hashes = pd.util.hash_pandas_object(selected, index=True).to_numpy().tobytes()
    return hashlib.sha256(hashes).hexdigest()[:16]


def write_json(payload: dict[str, object], path: str | Path) -> None:
    def default(value: object) -> object:
        if isinstance(value, (np.integer, np.floating)):
            return value.item()
        if isinstance(value, pd.Timestamp):
            return value.isoformat()
        raise TypeError(f"Cannot serialize {type(value).__name__}")

    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(json.dumps(payload, indent=2, sort_keys=True, default=default), encoding="utf-8")
