from pathlib import Path
from unittest.mock import patch

import pytest

from tradedesk import run_strategies
from tradedesk.marketdata import CandleClose, MarketData
from tradedesk.providers.backtest.client import BacktestClient
from tradedesk.strategy import BaseStrategy
from tradedesk.subscriptions import ChartSubscription, MarketSubscription
from tradedesk.providers.backtest.streamer import _parse_ts


def test_parse_ts_accepts_slashes_and_z():
    dt = _parse_ts("2025/12/04T19:20:00Z")
    assert dt.isoformat() == "2025-12-04T19:20:00+00:00"

def test_parse_ts_accepts_iso_z():
    dt = _parse_ts("2025-12-04T19:20:00Z")
    assert dt.isoformat() == "2025-12-04T19:20:00+00:00"

def test_parse_ts_accepts_space_and_z():
    dt = _parse_ts("2025-12-04 19:20:00Z")
    assert dt.isoformat() == "2025-12-04T19:20:00+00:00"
    
@pytest.mark.asyncio
async def test_backtest_trade_has_timestamp():
    instrument = "EPIC"
    client = BacktestClient(candle_series=[], market_series=[])
    await client.start()

    client._set_mark_price(instrument, 100.0)
    client._set_current_timestamp("2025-01-01T00:00:00Z")

    await client.place_market_order(instrument, "BUY", 1.0)

    assert client.trades[0].timestamp == "2025-01-01T00:00:00Z"

def test_backtest_from_csv_replays_and_trades(tmp_path: Path):
    csv_path = tmp_path / "candles.csv"
    csv_path.write_text(
        "timestamp,open,high,low,close,volume\n"
        "2025-12-28T00:00:00Z,10,10,10,10,0\n"
        "2025-12-28T00:05:00Z,11,11,11,11,0\n"
        "2025-12-28T00:10:00Z,12,12,12,12,0\n"
    )

    created = {}

    class TradeOnFirstLast(BaseStrategy):
        SUBSCRIPTIONS = [ChartSubscription("EPIC", "5MINUTE")]

        async def on_price_update(self, market_data: MarketData):
            pass

        async def on_candle_close(self, candle_close: CandleClose):
            if candle_close.candle.close == 10:
                await self.client.place_market_order(instrument=candle_close.instrument, direction="BUY", size=1.0)
            if candle_close.candle.close == 12:
                await self.client.place_market_order(instrument=candle_close.instrument, direction="SELL", size=1.0)
            await super().on_candle_close(candle_close)

    def factory():
        c = BacktestClient.from_csv(csv_path, instrument="EPIC", period="5MINUTE")
        created["client"] = c
        return c

    with patch("sys.exit") as _:
        run_strategies(
            strategy_specs=[TradeOnFirstLast],
            client_factory=factory,
            setup_logging=False,
        )

    client: BacktestClient = created["client"]  # type: ignore[assignment]
    assert len(client.trades) == 2
    assert client.realised_pnl == 2.0
    assert client.positions == {}

def test_backtest_market_csvs_drive_price_updates_and_signals(tmp_path: Path):
    gbp = tmp_path / "gbp.csv"
    eur = tmp_path / "eur.csv"

    gbp.write_text(
        "timestamp,bid,offer\n"
        "2025-12-01T09:00:00Z,1.25000,1.25020\n"
        "2025-12-01T09:01:00Z,1.25010,1.25030\n"
        "2025-12-01T09:02:00Z,1.25020,1.25040\n"
        "2025-12-01T09:03:00Z,1.25030,1.25050\n"
        "2025-12-01T09:04:00Z,1.25040,1.25060\n"
        "2025-12-01T09:05:00Z,1.25050,1.25070\n"
        "2025-12-01T09:06:00Z,1.25060,1.25080\n"
        "2025-12-01T09:07:00Z,1.25070,1.25090\n"
        "2025-12-01T09:08:00Z,1.25080,1.25100\n"
        "2025-12-01T09:09:00Z,1.25100,1.25120\n"
        "2025-12-01T09:10:00Z,1.25120,1.25140\n"
    )

    eur.write_text(
        "timestamp,bid,offer\n"
        "2025-12-01T09:00:00Z,1.10000,1.10020\n"
        "2025-12-01T09:01:00Z,1.09990,1.10010\n"
        "2025-12-01T09:02:00Z,1.09980,1.10000\n"
        "2025-12-01T09:03:00Z,1.09970,1.09990\n"
        "2025-12-01T09:04:00Z,1.09960,1.09980\n"
        "2025-12-01T09:05:00Z,1.09950,1.09970\n"
        "2025-12-01T09:06:00Z,1.09940,1.09960\n"
        "2025-12-01T09:07:00Z,1.09930,1.09950\n"
        "2025-12-01T09:08:00Z,1.09920,1.09940\n"
        "2025-12-01T09:09:00Z,1.09900,1.09920\n"
        "2025-12-01T09:10:00Z,1.09880,1.09900\n"
    )

    class MomentumLike(BaseStrategy):
        SUBSCRIPTIONS = [
            MarketSubscription("CS.D.GBPUSD.TODAY.IP"),
            MarketSubscription("CS.D.EURUSD.TODAY.IP"),
        ]

        def __init__(self, client, lookback=10):
            super().__init__(client)
            self.lookback = lookback
            self.prices: dict[str, list[float]] = {s.instrument: [] for s in self.SUBSCRIPTIONS}
            self.signals: list[tuple[str, str]] = []

        async def on_price_update(self, md: MarketData):
            mid = (md.bid + md.offer) / 2
            self.prices[md.instrument].append(mid)
            if len(self.prices[md.instrument]) < self.lookback:
                return
            window = self.prices[md.instrument][-self.lookback:]
            momentum = (window[-1] - window[0]) / window[0]
            if momentum > 0.001:
                self.signals.append((md.instrument, "UP"))
            elif momentum < -0.001:
                self.signals.append((md.instrument, "DOWN"))

    created = {}

    def factory():
        c = BacktestClient.from_market_csvs(
            {
                "CS.D.GBPUSD.TODAY.IP": gbp,
                "CS.D.EURUSD.TODAY.IP": eur,
            }
        )
        created["client"] = c
        return c

    with patch("sys.exit") as _:
        run_strategies(strategy_specs=[MomentumLike], client_factory=factory, setup_logging=False)

    # Verify at least one UP for GBP and one DOWN for EUR occurred
    # (The strategy instance is internal; so assert indirectly via client mark prices after replay)
    client = created["client"]
    assert client._mark_price["CS.D.GBPUSD.TODAY.IP"] > 1.25
    assert client._mark_price["CS.D.EURUSD.TODAY.IP"] < 1.10
