from __future__ import annotations

import shutil
from datetime import UTC, datetime
from pathlib import Path

import pandas as pd

from psx_signal.config import Settings
from psx_signal.data.hash import sha256_file
from psx_signal.data.providers import CsvDataProvider
from psx_signal.features import build_features
from psx_signal.labels import add_forward_labels


def import_csv(source: str | Path, raw_directory: str | Path = "data/raw") -> Path:
    source_path = Path(source)
    if not source_path.is_file():
        raise FileNotFoundError(source)
    digest = sha256_file(source_path)[:12]
    stamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
    destination = Path(raw_directory) / f"{stamp}_{digest}_{source_path.name}"
    destination.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(source_path, destination)
    destination.chmod(0o444)
    return destination


def create_feature_dataset(
    data_path: str | Path,
    benchmark_path: str | Path | None,
    settings: Settings,
    horizons: tuple[int, ...] = (1, 5),
    universe: tuple[str, ...] | None = None,
) -> pd.DataFrame:
    provider = CsvDataProvider(data_path, benchmark_path)
    ohlcv = provider.get_ohlcv()
    benchmark = provider.get_index_history(settings.benchmark_symbol)
    securities = ohlcv[ohlcv["symbol"] != settings.benchmark_symbol].copy()
    selected = tuple(symbol.upper() for symbol in (universe if universe is not None else settings.universe))
    if selected:
        securities = securities[securities["symbol"].isin(selected)]
    if securities.empty:
        raise ValueError("No security rows remain after excluding benchmark")
    features = build_features(securities, benchmark)
    labelled = add_forward_labels(
        features,
        horizons=horizons,
        buy_threshold=settings.labels.buy_threshold,
        sell_threshold=settings.labels.sell_threshold,
    )
    benchmark_labels = add_forward_labels(
        benchmark.assign(symbol=settings.benchmark_symbol),
        horizons=horizons,
        buy_threshold=settings.labels.buy_threshold,
        sell_threshold=settings.labels.sell_threshold,
    )
    benchmark_columns = ["date"] + [f"forward_return_{horizon}d" for horizon in horizons]
    benchmark_targets = benchmark_labels[benchmark_columns].rename(columns={
        f"forward_return_{horizon}d": f"benchmark_forward_return_{horizon}d" for horizon in horizons
    })
    return labelled.merge(benchmark_targets, on="date", how="left", validate="many_to_one")


def write_parquet(frame: pd.DataFrame, path: str | Path) -> Path:
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)
    frame.to_parquet(target, index=False)
    return target
