"""Tests for feed types (v0.4.6+)."""

import json
import time

import pytest

from horizon._horizon import Engine, RiskConfig
from horizon.feeds import (
    PredictItFeed,
    ManifoldFeed,
    ESPNFeed,
    NWSFeed,
    RESTJsonPathFeed,
    ChainlinkFeed,
)


class TestFeedDataclasses:
    """Test that feed dataclasses have correct _type fields and defaults."""

    def test_predictit_feed(self):
        f = PredictItFeed(market_id=7456, contract_id=28562)
        assert f._type == "predictit"
        assert f.market_id == 7456
        assert f.contract_id == 28562
        assert f.interval == 5.0

    def test_predictit_feed_no_contract(self):
        f = PredictItFeed(market_id=7456)
        assert f.contract_id is None

    def test_manifold_feed(self):
        f = ManifoldFeed(slug="will-btc-hit-100k")
        assert f._type == "manifold"
        assert f.slug == "will-btc-hit-100k"
        assert f.interval == 5.0

    def test_espn_feed(self):
        f = ESPNFeed(sport="basketball", league="nba")
        assert f._type == "espn"
        assert f.sport == "basketball"
        assert f.league == "nba"
        assert f.event_id is None
        assert f.interval == 10.0

    def test_espn_feed_with_event(self):
        f = ESPNFeed(sport="football", league="nfl", event_id="401585601")
        assert f.event_id == "401585601"

    def test_nws_feed_forecast(self):
        f = NWSFeed(office="TOP", grid_x=31, grid_y=80)
        assert f._type == "nws"
        assert f.mode == "forecast"
        assert f.office == "TOP"
        assert f.grid_x == 31
        assert f.grid_y == 80
        assert f.interval == 60.0

    def test_nws_feed_alerts(self):
        f = NWSFeed(state="FL", mode="alerts")
        assert f.mode == "alerts"
        assert f.state == "FL"

    def test_nws_feed_user_agent(self):
        f = NWSFeed()
        assert f.user_agent == "Horizon-SDK/0.4.7"

    def test_rest_json_path_feed(self):
        f = RESTJsonPathFeed(
            url="https://api.example.com/data",
            price_path="bitcoin.usd",
        )
        assert f._type == "rest_json_path"
        assert f.url == "https://api.example.com/data"
        assert f.price_path == "bitcoin.usd"
        assert f.bid_path is None
        assert f.ask_path is None
        assert f.volume_path is None
        assert f.interval == 5.0

    def test_rest_json_path_all_paths(self):
        f = RESTJsonPathFeed(
            url="https://api.example.com/data",
            price_path="data.price",
            bid_path="data.bid",
            ask_path="data.ask",
            volume_path="data.vol",
            interval=10.0,
        )
        assert f.price_path == "data.price"
        assert f.bid_path == "data.bid"
        assert f.ask_path == "data.ask"
        assert f.volume_path == "data.vol"
        assert f.interval == 10.0


class TestEngineNewFeedTypes:
    """Test that Engine.start_feed accepts new feed types with config_json."""

    def test_predictit_feed_starts(self):
        engine = Engine()
        config = json.dumps({"market_id": 7456, "contract_id": 28562})
        engine.start_feed("pi", "predictit", config_json=config)
        time.sleep(0.1)
        assert engine.is_feed_running("pi")
        engine.stop_feeds()

    def test_manifold_feed_starts(self):
        engine = Engine()
        config = json.dumps({"slug": "test-market"})
        engine.start_feed("mf", "manifold", config_json=config)
        time.sleep(0.1)
        assert engine.is_feed_running("mf")
        engine.stop_feeds()

    def test_espn_feed_starts(self):
        engine = Engine()
        config = json.dumps({"sport": "basketball", "league": "nba"})
        engine.start_feed("nba", "espn", config_json=config)
        time.sleep(0.1)
        assert engine.is_feed_running("nba")
        engine.stop_feeds()

    def test_nws_feed_starts(self):
        engine = Engine()
        config = json.dumps({
            "mode": "alerts",
            "state": "FL",
        })
        engine.start_feed("weather", "nws", config_json=config)
        time.sleep(0.1)
        assert engine.is_feed_running("weather")
        engine.stop_feeds()

    def test_rest_json_path_feed_starts(self):
        engine = Engine()
        config = json.dumps({
            "url": "https://api.example.com/data",
            "price_path": "bitcoin.usd",
        })
        engine.start_feed("custom", "rest_json_path", config_json=config)
        time.sleep(0.1)
        assert engine.is_feed_running("custom")
        engine.stop_feeds()

    def test_predictit_missing_config_json(self):
        engine = Engine()
        with pytest.raises(ValueError, match="requires config_json"):
            engine.start_feed("pi", "predictit")

    def test_predictit_missing_market_id(self):
        engine = Engine()
        with pytest.raises(ValueError, match="market_id"):
            engine.start_feed("pi", "predictit", config_json=json.dumps({}))

    def test_manifold_missing_slug(self):
        engine = Engine()
        with pytest.raises(ValueError, match="slug"):
            engine.start_feed("mf", "manifold", config_json=json.dumps({}))

    def test_espn_missing_sport(self):
        engine = Engine()
        with pytest.raises(ValueError, match="sport"):
            engine.start_feed("espn", "espn", config_json=json.dumps({"league": "nba"}))

    def test_espn_missing_league(self):
        engine = Engine()
        with pytest.raises(ValueError, match="league"):
            engine.start_feed("espn", "espn", config_json=json.dumps({"sport": "basketball"}))

    def test_rest_json_path_missing_url(self):
        engine = Engine()
        with pytest.raises(ValueError, match="url"):
            engine.start_feed("custom", "rest_json_path", config_json=json.dumps({}))

    def test_invalid_config_json(self):
        engine = Engine()
        with pytest.raises(ValueError, match="invalid config_json"):
            engine.start_feed("pi", "predictit", config_json="not json")

    def test_backward_compatible_existing_feeds(self):
        """Existing feed types still work without config_json."""
        engine = Engine()
        engine.start_feed("btc", "binance_ws", symbol="btcusdt")
        time.sleep(0.1)
        assert engine.is_feed_running("btc")
        engine.stop_feeds()

    def test_snapshot_has_default_for_new_feed(self):
        engine = Engine()
        config = json.dumps({"slug": "test-market"})
        engine.start_feed("mf", "manifold", config_json=config)
        time.sleep(0.1)
        snap = engine.feed_snapshot("mf")
        assert snap is not None
        assert isinstance(snap.price, float)
        engine.stop_feeds()


class TestStartFeedsIntegration:
    """Test the _start_feeds function with mocked engine."""

    def test_start_feeds_predictit(self):
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds

        engine = MagicMock()
        feeds = {"pi": PredictItFeed(market_id=7456, contract_id=28562)}
        _start_feeds(engine, feeds)

        engine.start_feed.assert_called_once()
        call_args = engine.start_feed.call_args
        assert call_args[0][0] == "pi"
        assert call_args[0][1] == "predictit"
        cfg = json.loads(call_args[1]["config_json"])
        assert cfg["market_id"] == 7456
        assert cfg["contract_id"] == 28562

    def test_start_feeds_manifold(self):
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds

        engine = MagicMock()
        feeds = {"mf": ManifoldFeed(slug="test-market")}
        _start_feeds(engine, feeds)

        engine.start_feed.assert_called_once()
        cfg = json.loads(engine.start_feed.call_args[1]["config_json"])
        assert cfg["slug"] == "test-market"

    def test_start_feeds_espn(self):
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds

        engine = MagicMock()
        feeds = {"nba": ESPNFeed("basketball", "nba", event_id="12345")}
        _start_feeds(engine, feeds)

        engine.start_feed.assert_called_once()
        cfg = json.loads(engine.start_feed.call_args[1]["config_json"])
        assert cfg["sport"] == "basketball"
        assert cfg["league"] == "nba"
        assert cfg["event_id"] == "12345"

    def test_start_feeds_nws(self):
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds

        engine = MagicMock()
        feeds = {"wx": NWSFeed(state="FL", mode="alerts")}
        _start_feeds(engine, feeds)

        engine.start_feed.assert_called_once()
        cfg = json.loads(engine.start_feed.call_args[1]["config_json"])
        assert cfg["mode"] == "alerts"
        assert cfg["state"] == "FL"

    def test_start_feeds_rest_json_path(self):
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds

        engine = MagicMock()
        feeds = {
            "btc": RESTJsonPathFeed(
                url="https://api.example.com/data",
                price_path="bitcoin.usd",
            )
        }
        _start_feeds(engine, feeds)

        engine.start_feed.assert_called_once()
        cfg = json.loads(engine.start_feed.call_args[1]["config_json"])
        assert cfg["url"] == "https://api.example.com/data"
        assert cfg["price_path"] == "bitcoin.usd"
        assert "bid_path" not in cfg
        assert "ask_path" not in cfg

    def test_start_feeds_mixed(self):
        """Test mixed old and new feed types in one call."""
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds
        from horizon.feeds import BinanceWS

        engine = MagicMock()
        feeds = {
            "btc": BinanceWS("btcusdt"),
            "pi": PredictItFeed(market_id=7456),
            "mf": ManifoldFeed(slug="test"),
        }
        _start_feeds(engine, feeds)

        assert engine.start_feed.call_count == 3


class TestChainlinkFeedDataclass:
    """Test ChainlinkFeed dataclass defaults and fields."""

    def test_chainlink_feed_defaults(self):
        f = ChainlinkFeed(
            contract_address="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
            rpc_url="https://eth.llamarpc.com",
        )
        assert f._type == "chainlink"
        assert f.contract_address == "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"
        assert f.rpc_url == "https://eth.llamarpc.com"
        assert f.decimals == 8
        assert f.interval == 10.0

    def test_chainlink_feed_custom_decimals(self):
        f = ChainlinkFeed(
            contract_address="0xABC",
            rpc_url="https://rpc.example.com",
            decimals=18,
            interval=30.0,
        )
        assert f.decimals == 18
        assert f.interval == 30.0


class TestChainlinkEngineIntegration:
    """Test Engine.start_feed with chainlink feed type."""

    def test_chainlink_feed_starts(self):
        engine = Engine()
        config = json.dumps({
            "contract_address": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
            "rpc_url": "https://eth.llamarpc.com",
        })
        engine.start_feed("eth_usd", "chainlink", config_json=config)
        time.sleep(0.1)
        assert engine.is_feed_running("eth_usd")
        engine.stop_feeds()

    def test_chainlink_feed_custom_decimals(self):
        engine = Engine()
        config = json.dumps({
            "contract_address": "0xABC",
            "rpc_url": "https://rpc.example.com",
            "decimals": 18,
            "interval": 30.0,
        })
        engine.start_feed("custom", "chainlink", config_json=config)
        time.sleep(0.1)
        assert engine.is_feed_running("custom")
        engine.stop_feeds()

    def test_chainlink_missing_contract_address(self):
        engine = Engine()
        with pytest.raises(ValueError, match="contract_address"):
            engine.start_feed("cl", "chainlink", config_json=json.dumps({
                "rpc_url": "https://eth.llamarpc.com",
            }))

    def test_chainlink_missing_rpc_url(self):
        engine = Engine()
        with pytest.raises(ValueError, match="rpc_url"):
            engine.start_feed("cl", "chainlink", config_json=json.dumps({
                "contract_address": "0xABC",
            }))

    def test_chainlink_missing_config_json(self):
        engine = Engine()
        with pytest.raises(ValueError, match="requires config_json"):
            engine.start_feed("cl", "chainlink")

    def test_chainlink_snapshot_has_default(self):
        engine = Engine()
        config = json.dumps({
            "contract_address": "0xABC",
            "rpc_url": "https://rpc.example.com",
        })
        engine.start_feed("cl", "chainlink", config_json=config)
        time.sleep(0.1)
        snap = engine.feed_snapshot("cl")
        assert snap is not None
        assert isinstance(snap.price, float)
        engine.stop_feeds()


class TestChainlinkStartFeedsIntegration:
    """Test _start_feeds with ChainlinkFeed config."""

    def test_start_feeds_chainlink(self):
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds

        engine = MagicMock()
        feeds = {
            "eth_usd": ChainlinkFeed(
                contract_address="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
                rpc_url="https://eth.llamarpc.com",
            )
        }
        _start_feeds(engine, feeds)

        engine.start_feed.assert_called_once()
        call_args = engine.start_feed.call_args
        assert call_args[0][0] == "eth_usd"
        assert call_args[0][1] == "chainlink"
        cfg = json.loads(call_args[1]["config_json"])
        assert cfg["contract_address"] == "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"
        assert cfg["rpc_url"] == "https://eth.llamarpc.com"
        assert cfg["decimals"] == 8
        assert cfg["interval"] == 10.0

    def test_start_feeds_chainlink_custom(self):
        from unittest.mock import MagicMock
        from horizon.strategy import _start_feeds

        engine = MagicMock()
        feeds = {
            "link": ChainlinkFeed(
                contract_address="0xABC",
                rpc_url="https://rpc.example.com",
                decimals=18,
                interval=30.0,
            )
        }
        _start_feeds(engine, feeds)

        cfg = json.loads(engine.start_feed.call_args[1]["config_json"])
        assert cfg["decimals"] == 18
        assert cfg["interval"] == 30.0
