"""
NeuralFlow AlphaDeck v2.0 — Price Feed Adapter

Standardizes external price feeds (from any API) into NeuralFlow-compatible schema.
Use this to plug in your own live or historical data without touching the core parquet files.

Example:
    from pricefeed_adapter import normalize_price_df
    from enterprise_loader import EnterpriseLoader

    df_raw = your_api.get_bars("BTCUSDT")  # your own API call
    df_norm = normalize_price_df(df_raw, asset_class="crypto")
    
    loader = EnterpriseLoader()
    loader.register_price_df("live_btc", df_norm)
    loader.query("SELECT * FROM live_btc LIMIT 10")
"""

from __future__ import annotations
from dataclasses import dataclass
from typing import Literal, Protocol, List, Optional
import pandas as pd

AssetClass = Literal["crypto", "forex", "indices", "equity"]

REQUIRED_COLS = ["timestamp", "symbol", "open", "high", "low", "close", "volume"]


@dataclass
class PriceBar:
    """Single OHLCV bar in standardized format."""
    timestamp: pd.Timestamp
    symbol: str
    open: float
    high: float
    low: float
    close: float
    volume: float
    asset_class: AssetClass


class PriceFeed(Protocol):
    """Protocol for user-implemented price feeds."""
    
    def fetch_history(self, symbol: str, **kwargs) -> pd.DataFrame:
        """
        Return historical bars as a DataFrame with at least:
        timestamp/datetime, open, high, low, close, volume.
        """
        ...


def normalize_price_df(
    df: pd.DataFrame,
    asset_class: AssetClass,
    symbol: Optional[str] = None,
    ts_col: str = "timestamp",
) -> pd.DataFrame:
    """
    Normalize an arbitrary price DataFrame into NeuralFlow-compatible schema.

    Parameters
    ----------
    df : pd.DataFrame
        Raw price data from any source.
    asset_class : AssetClass
        One of: "crypto", "forex", "indices", "equity"
    symbol : str, optional
        If provided, sets the symbol column (useful if df doesn't have one).
    ts_col : str
        Name of the timestamp column in the input DataFrame.

    Returns
    -------
    pd.DataFrame
        Normalized DataFrame ready for DuckDB registration.

    Example
    -------
    >>> df_raw = pd.DataFrame({
    ...     "timestamp": pd.date_range("2025-01-01", periods=10, freq="T"),
    ...     "open": 50000.0, "high": 50100.0, "low": 49900.0, "close": 50050.0,
    ...     "volume": 1.23, "symbol": "BTCUSDT"
    ... })
    >>> df_norm = normalize_price_df(df_raw, asset_class="crypto")
    """
    df = df.copy()

    # Normalize timestamp column name
    if ts_col not in df.columns:
        # Try common fallbacks
        for candidate in ["datetime", "date", "time", "Date", "Time", "Timestamp"]:
            if candidate in df.columns:
                ts_col = candidate
                break
        else:
            raise ValueError(
                f"Could not find timestamp column. Tried: {ts_col}, datetime, date, time. "
                f"Available columns: {list(df.columns)}"
            )

    df[ts_col] = pd.to_datetime(df[ts_col], errors="coerce")
    df = df.rename(columns={ts_col: "timestamp"})

    # Basic OHLCV coercion
    for col in ["open", "high", "low", "close", "volume"]:
        if col in df.columns:
            df[col] = pd.to_numeric(df[col], errors="coerce")
        else:
            # Default to 0.0 for missing columns (volume often missing in FX)
            df[col] = 0.0

    # Handle symbol column
    if "symbol" not in df.columns:
        if symbol is not None:
            df["symbol"] = symbol
        else:
            raise ValueError(
                "Expected a 'symbol' column in price DataFrame, or pass symbol= argument."
            )
    
    # If symbol argument provided, override
    if symbol is not None:
        df["symbol"] = symbol

    # Drop rows with missing critical data
    df = df.dropna(subset=["timestamp", "close"])
    df["asset_class"] = asset_class

    # Build output columns
    out_cols = ["timestamp", "symbol", "open", "high", "low", "close", "volume", "asset_class"]

    # For equity, we keep both `datetime` and `timestamp` for compatibility
    if asset_class == "equity":
        df["datetime"] = df["timestamp"]
        out_cols.append("datetime")

    return df[out_cols].reset_index(drop=True)


def validate_price_df(df: pd.DataFrame) -> List[str]:
    """
    Validate a normalized price DataFrame and return list of issues (empty = valid).
    
    Parameters
    ----------
    df : pd.DataFrame
        DataFrame to validate.
    
    Returns
    -------
    List[str]
        List of validation issues. Empty list means valid.
    """
    issues = []
    
    for col in REQUIRED_COLS:
        if col not in df.columns:
            issues.append(f"Missing required column: {col}")
    
    if "timestamp" in df.columns and df["timestamp"].isna().any():
        issues.append(f"Found {df['timestamp'].isna().sum()} null timestamps")
    
    if "close" in df.columns and df["close"].isna().any():
        issues.append(f"Found {df['close'].isna().sum()} null close prices")
    
    if "asset_class" not in df.columns:
        issues.append("Missing 'asset_class' column - did you run normalize_price_df()?")
    
    return issues


# =============================================================================
# Example Feed Stubs (No real API calls - templates for users to implement)
# =============================================================================

class BinanceCryptoFeed:
    """
    Example stub for a user-provided crypto feed (e.g., Binance).
    User should implement the actual API calls.
    
    Example implementation:
    
        import requests
        
        class MyBinanceFeed(BinanceCryptoFeed):
            def fetch_history(self, symbol: str, interval: str = "1m", limit: int = 1000):
                url = f"https://api.binance.com/api/v3/klines"
                params = {"symbol": symbol, "interval": interval, "limit": limit}
                resp = requests.get(url, params=params)
                data = resp.json()
                df = pd.DataFrame(data, columns=[
                    "open_time", "open", "high", "low", "close", "volume",
                    "close_time", "quote_volume", "trades", "taker_buy_base",
                    "taker_buy_quote", "ignore"
                ])
                df["timestamp"] = pd.to_datetime(df["open_time"], unit="ms")
                df["symbol"] = symbol
                return df[["timestamp", "symbol", "open", "high", "low", "close", "volume"]]
    """
    
    def fetch_history(
        self, 
        symbol: str, 
        interval: str = "1m", 
        limit: int = 1000
    ) -> pd.DataFrame:
        raise NotImplementedError(
            "Implement your own Binance API calls here. "
            "See class docstring for example."
        )


class AlpacaEquityFeed:
    """
    Example stub for Alpaca equity feed.
    User should implement actual API calls using alpaca-trade-api.
    """
    
    def fetch_history(
        self,
        symbol: str,
        start: str = None,
        end: str = None,
        timeframe: str = "1Min",
    ) -> pd.DataFrame:
        raise NotImplementedError(
            "Implement your own Alpaca API calls here. "
            "pip install alpaca-trade-api, then use api.get_bars()."
        )


class OandaForexFeed:
    """
    Example stub for OANDA forex feed.
    User should implement actual API calls.
    """
    
    def fetch_history(
        self,
        symbol: str,
        granularity: str = "M1",
        count: int = 500,
    ) -> pd.DataFrame:
        raise NotImplementedError(
            "Implement your own OANDA API calls here."
        )


class GenericRESTFeed:
    """
    Generic REST adapter: user can subclass and implement `fetch_history`.
    
    Example:
    
        class MyFeed(GenericRESTFeed):
            def fetch_history(self, symbol: str, **kwargs):
                resp = requests.get(f"https://my-api.com/bars/{symbol}")
                return pd.DataFrame(resp.json())
    """
    
    def fetch_history(self, symbol: str, **kwargs) -> pd.DataFrame:
        raise NotImplementedError(
            "Subclass GenericRESTFeed and implement fetch_history()."
        )


# =============================================================================
# Convenience function for quick testing
# =============================================================================

def make_dummy_bars(
    symbol: str,
    asset_class: AssetClass,
    n_bars: int = 100,
    start: str = "2025-01-01",
    freq: str = "1min",
    base_price: float = 100.0,
) -> pd.DataFrame:
    """
    Generate dummy price bars for testing.
    
    Parameters
    ----------
    symbol : str
        Symbol name.
    asset_class : AssetClass
        One of: "crypto", "forex", "indices", "equity"
    n_bars : int
        Number of bars to generate.
    start : str
        Start timestamp.
    freq : str
        Bar frequency (pandas frequency string).
    base_price : float
        Base price around which to generate random bars.
    
    Returns
    -------
    pd.DataFrame
        Normalized DataFrame ready for DuckDB registration.
    """
    import numpy as np
    
    np.random.seed(42)
    timestamps = pd.date_range(start, periods=n_bars, freq=freq)
    
    # Random walk for close prices
    returns = np.random.randn(n_bars) * 0.001
    close = base_price * np.cumprod(1 + returns)
    
    # Generate OHLC from close
    high = close * (1 + np.abs(np.random.randn(n_bars) * 0.001))
    low = close * (1 - np.abs(np.random.randn(n_bars) * 0.001))
    open_ = np.roll(close, 1)
    open_[0] = base_price
    
    df = pd.DataFrame({
        "timestamp": timestamps,
        "symbol": symbol,
        "open": open_,
        "high": high,
        "low": low,
        "close": close,
        "volume": np.random.uniform(100, 10000, n_bars),
    })
    
    return normalize_price_df(df, asset_class=asset_class)


if __name__ == "__main__":
    # Quick test
    df = make_dummy_bars("BTCUSDT", "crypto", n_bars=10)
    print("Dummy bars:")
    print(df)
    print("\nValidation:", validate_price_df(df))
