import numpy as np
import pandas as pd
from .util import tf_minutes

COLS = ["open", "high", "low", "close", "volume"]


def _frame(rows):
    df = pd.DataFrame(rows, columns=["ts"] + COLS)
    df.index = pd.to_datetime(df.pop("ts"), unit="ms", utc=True)
    return df[~df.index.duplicated()].sort_index().astype(float)


def public_exchange(cfg):
    if cfg.get("platform") == "mt5":
        return None  # MT5 data comes from the terminal, not an exchange object
    import ccxt
    return getattr(ccxt, cfg["exchange"])({"enableRateLimit": True,
                                           "options": {"defaultType": cfg.get("market_type", "swap")}})


def mt5_candles(cfg, bars):
    from .mt5conn import get_mt5, tf_name
    m = get_mt5()
    m.symbol_select(cfg["symbol"], True)
    rates = m.copy_rates_from_pos(cfg["symbol"], getattr(m, "TIMEFRAME_" + tf_name(cfg["primary_timeframe"])), 0, int(bars))
    if rates is None or len(rates) == 0:
        raise RuntimeError(f"MT5 returned no candles for {cfg['symbol']}: {m.last_error()}")
    df = pd.DataFrame(rates)
    df.index = pd.to_datetime(df.pop("time"), unit="s", utc=True)
    return df.rename(columns={"tick_volume": "volume"})[COLS].astype(float)


def get_price(cfg, ex=None):
    """Current price. For MT5 it is the middle of bid and ask."""
    if cfg.get("platform") == "mt5":
        from .mt5conn import get_mt5
        t = get_mt5().symbol_info_tick(cfg["symbol"])
        if t is None:
            raise RuntimeError(f"no MT5 price for {cfg['symbol']}")
        return (t.bid + t.ask) / 2
    return float(ex.fetch_ticker(cfg["symbol"])["last"])


def fetch_candles(cfg, ex=None):
    """Latest candles with the still-forming one removed, so signals use CLOSED candles only."""
    if cfg.get("platform") == "mt5":
        return mt5_candles(cfg, cfg["candles"]).iloc[:-1]
    ex = ex or public_exchange(cfg)
    df = _frame(ex.fetch_ohlcv(cfg["symbol"], cfg["primary_timeframe"], limit=cfg["candles"]))
    return df.iloc[:-1]


def fetch_history(cfg, days=60, ex=None):
    if cfg.get("platform") == "mt5":
        return mt5_candles(cfg, min(days * 1440 // tf_minutes(cfg["primary_timeframe"]), 50000)).iloc[:-1]
    ex = ex or public_exchange(cfg)
    tf = cfg["primary_timeframe"]
    step = tf_minutes(tf) * 60 * 1000
    since = int(pd.Timestamp.now(tz="UTC").timestamp() * 1000) - days * 86400 * 1000
    rows = []
    while True:
        batch = ex.fetch_ohlcv(cfg["symbol"], tf, since=since, limit=1000)
        if not batch:
            break
        rows += batch
        nxt = batch[-1][0] + step
        if nxt <= since or len(batch) < 2:
            break
        since = nxt
    return _frame(rows).iloc[:-1]


def load_csv(path):
    """CSV with a timestamp first column (ms since epoch or ISO text) then open,high,low,close,volume."""
    df = pd.read_csv(path)
    col = df.pop(df.columns[0])
    df.index = pd.to_datetime(col, unit="ms", utc=True) if pd.api.types.is_numeric_dtype(col) else pd.to_datetime(col, utc=True)
    return df[COLS].astype(float).sort_index()


def synthetic_candles(n=4000, seed=7, start=60000.0, tf="15m"):
    """Fake but regime-switching prices (trend up / trend down / range) for offline tests ONLY."""
    rng = np.random.default_rng(seed)
    drift, vol, i = np.zeros(n), np.zeros(n), 0
    while i < n:
        L = int(rng.integers(100, 400))
        kind = rng.choice(["up", "down", "range"])
        drift[i:i + L] = {"up": 0.0004, "down": -0.0004, "range": 0.0}[kind]
        vol[i:i + L] = {"up": 0.003, "down": 0.0035, "range": 0.0025}[kind]
        i += L
    ret = drift + vol * rng.standard_normal(n)
    close = start * np.exp(np.cumsum(ret))
    open_ = np.r_[start, close[:-1]]
    high = np.maximum(open_, close) + np.abs(rng.standard_normal(n)) * vol * close * 0.6
    low = np.minimum(open_, close) - np.abs(rng.standard_normal(n)) * vol * close * 0.6
    volume = (1 + np.abs(ret) / vol.mean() * 3) * rng.uniform(50, 150, n)
    idx = pd.date_range("2026-01-01", periods=n, freq=f"{tf_minutes(tf)}min", tz="UTC")
    return pd.DataFrame({"open": open_, "high": high, "low": low, "close": close, "volume": volume}, index=idx)
