"""Unit tests for watchlist management tools."""

from typing import Any
from unittest.mock import patch

import pytest

from open_stocks_mcp.tools.robinhood_watchlist_tools import (
    add_symbols_to_watchlist,
    get_all_watchlists,
    get_watchlist_by_name,
    get_watchlist_performance,
    remove_symbols_from_watchlist,
)


class TestGetAllWatchlists:
    """Test get all watchlists functionality."""

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_all_watchlists_success(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test successful retrieval of all watchlists."""
        mock_execute_with_retry.return_value = [
            {
                "name": "Tech Stocks",
                "url": "https://robinhood.com/watchlists/123/",
                "user": "https://robinhood.com/user/456/",
                "symbols": ["AAPL", "GOOGL", "MSFT"],
            },
            {
                "name": "Energy",
                "url": "https://robinhood.com/watchlists/789/",
                "user": "https://robinhood.com/user/456/",
                "symbols": ["XOM", "CVX"],
            },
        ]

        result = await get_all_watchlists()

        assert "result" in result
        assert result["result"]["total_watchlists"] == 2
        assert len(result["result"]["watchlists"]) == 2

        # Check that symbol_count is added
        assert result["result"]["watchlists"][0]["symbol_count"] == 3
        assert result["result"]["watchlists"][1]["symbol_count"] == 2
        assert result["result"]["watchlists"][0]["name"] == "Tech Stocks"
        assert result["result"]["status"] == "success"

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_all_watchlists_no_data(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test get all watchlists when no data is available."""
        mock_execute_with_retry.return_value = None

        result = await get_all_watchlists()

        assert "result" in result
        assert result["result"]["total_watchlists"] == 0
        assert result["result"]["watchlists"] == []
        assert result["result"]["status"] == "no_data"
        assert "No watchlists found" in result["result"]["message"]

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_all_watchlists_empty_list(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test get all watchlists with empty list."""
        mock_execute_with_retry.return_value = []

        result = await get_all_watchlists()

        assert "result" in result
        assert result["result"]["total_watchlists"] == 0
        assert result["result"]["watchlists"] == []
        # Empty list is treated as no_data by the function
        assert result["result"]["status"] == "no_data"

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_all_watchlists_missing_symbols(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test get all watchlists with missing symbols field."""
        mock_execute_with_retry.return_value = [
            {
                "name": "Empty Watchlist",
                "url": "https://robinhood.com/watchlists/999/",
                "user": "https://robinhood.com/user/456/",
                # No symbols field
            }
        ]

        result = await get_all_watchlists()

        assert "result" in result
        assert result["result"]["total_watchlists"] == 1
        assert result["result"]["watchlists"][0]["symbol_count"] == 0
        assert result["result"]["status"] == "success"


class TestGetWatchlistByName:
    """Test get watchlist by name functionality."""

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_by_name_success(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test successful retrieval of watchlist by name."""
        mock_execute_with_retry.return_value = {
            "name": "Tech Stocks",
            "url": "https://robinhood.com/watchlists/123/",
            "symbols": ["AAPL", "GOOGL", "MSFT"],
        }

        result = await get_watchlist_by_name("Tech Stocks")

        assert "result" in result
        assert result["result"]["name"] == "Tech Stocks"
        assert result["result"]["symbol_count"] == 3
        assert len(result["result"]["symbols"]) == 3
        assert "AAPL" in result["result"]["symbols"]
        assert result["result"]["status"] == "success"

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_by_name_not_found(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test get watchlist by name when watchlist not found."""
        mock_execute_with_retry.return_value = None

        result = await get_watchlist_by_name("NonExistent")

        assert "result" in result
        assert result["result"]["name"] == "NonExistent"
        assert result["result"]["symbol_count"] == 0
        assert result["result"]["symbols"] == []
        assert result["result"]["status"] == "not_found"
        assert "not found" in result["result"]["message"]

    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_by_name_empty_name(self) -> None:
        """Test get watchlist by name with empty name."""
        result = await get_watchlist_by_name("")

        assert "result" in result
        assert result["result"]["status"] == "error"
        assert "Watchlist name is required" in result["result"]["error"]

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_by_name_no_symbols(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test get watchlist by name with no symbols."""
        mock_execute_with_retry.return_value = {
            "name": "Empty Watchlist",
            "url": "https://robinhood.com/watchlists/999/",
            "symbols": [],
        }

        result = await get_watchlist_by_name("Empty Watchlist")

        assert "result" in result
        assert result["result"]["name"] == "Empty Watchlist"
        assert result["result"]["symbol_count"] == 0
        assert result["result"]["symbols"] == []
        assert result["result"]["status"] == "success"


class TestAddSymbolsToWatchlist:
    """Test add symbols to watchlist functionality."""

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_add_symbols_to_watchlist_success(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test successful addition of symbols to watchlist."""
        mock_execute_with_retry.return_value = {"success": True}

        result = await add_symbols_to_watchlist("Tech Stocks", ["AAPL", "GOOGL"])

        assert "result" in result
        assert result["result"]["watchlist_name"] == "Tech Stocks"
        assert result["result"]["symbols_added"] == ["AAPL", "GOOGL"]
        assert result["result"]["symbols_count"] == 2
        assert result["result"]["success"] is True
        assert result["result"]["status"] == "success"
        assert "Successfully added 2 symbols" in result["result"]["message"]

    @pytest.mark.exception_test
    @pytest.mark.skip(reason="Slow exception test - run with pytest -m exception_test")
    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_add_symbols_to_watchlist_failure(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test failed addition of symbols to watchlist."""
        mock_execute_with_retry.return_value = None

        result = await add_symbols_to_watchlist("Tech Stocks", ["AAPL"])

        assert "result" in result
        assert result["result"]["watchlist_name"] == "Tech Stocks"
        assert result["result"]["symbols_attempted"] == ["AAPL"]
        assert result["result"]["success"] is False
        assert result["result"]["status"] == "error"
        assert "Failed to add symbols" in result["result"]["error"]

    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_add_symbols_to_watchlist_empty_name(self) -> None:
        """Test add symbols with empty watchlist name."""
        result = await add_symbols_to_watchlist("", ["AAPL"])

        assert "result" in result
        assert result["result"]["status"] == "error"
        assert "Watchlist name is required" in result["result"]["error"]

    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_add_symbols_to_watchlist_empty_symbols(self) -> None:
        """Test add symbols with empty symbols list."""
        result = await add_symbols_to_watchlist("Tech Stocks", [])

        assert "result" in result
        assert result["result"]["status"] == "error"
        assert "At least one symbol is required" in result["result"]["error"]

    @pytest.mark.exception_test
    @pytest.mark.skip(reason="Slow exception test - run with pytest -m exception_test")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_add_symbols_to_watchlist_invalid_symbols(self) -> None:
        """Test add symbols with invalid symbols."""
        result = await add_symbols_to_watchlist("Tech Stocks", ["", "  ", None])

        assert "result" in result
        assert result["result"]["status"] == "error"
        assert "No valid symbols provided" in result["result"]["error"]

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_add_symbols_to_watchlist_format_symbols(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test symbol formatting (lowercase to uppercase, strip whitespace)."""
        mock_execute_with_retry.return_value = {"success": True}

        result = await add_symbols_to_watchlist("Tech Stocks", [" aapl ", "googl"])

        assert "result" in result
        assert result["result"]["symbols_added"] == ["AAPL", "GOOGL"]
        # Check that the execute_with_retry was called with the correct parameters
        mock_execute_with_retry.assert_called_once()


class TestRemoveSymbolsFromWatchlist:
    """Test remove symbols from watchlist functionality."""

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_remove_symbols_from_watchlist_success(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test successful removal of symbols from watchlist."""
        mock_execute_with_retry.return_value = {"success": True}

        result = await remove_symbols_from_watchlist("Tech Stocks", ["AAPL", "GOOGL"])

        assert "result" in result
        assert result["result"]["watchlist_name"] == "Tech Stocks"
        assert result["result"]["symbols_removed"] == ["AAPL", "GOOGL"]
        assert result["result"]["symbols_count"] == 2
        assert result["result"]["success"] is True
        assert result["result"]["status"] == "success"
        assert "Successfully removed 2 symbols" in result["result"]["message"]

    @pytest.mark.exception_test
    @pytest.mark.skip(reason="Slow exception test - run with pytest -m exception_test")
    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_remove_symbols_from_watchlist_failure(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test failed removal of symbols from watchlist."""
        mock_execute_with_retry.return_value = None

        result = await remove_symbols_from_watchlist("Tech Stocks", ["AAPL"])

        assert "result" in result
        assert result["result"]["watchlist_name"] == "Tech Stocks"
        assert result["result"]["symbols_attempted"] == ["AAPL"]
        assert result["result"]["success"] is False
        assert result["result"]["status"] == "error"
        assert "Failed to remove symbols" in result["result"]["error"]

    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_remove_symbols_from_watchlist_empty_name(self) -> None:
        """Test remove symbols with empty watchlist name."""
        result = await remove_symbols_from_watchlist("", ["AAPL"])

        assert "result" in result
        assert result["result"]["status"] == "error"
        assert "Watchlist name is required" in result["result"]["error"]

    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_remove_symbols_from_watchlist_empty_symbols(self) -> None:
        """Test remove symbols with empty symbols list."""
        result = await remove_symbols_from_watchlist("Tech Stocks", [])

        assert "result" in result
        assert result["result"]["status"] == "error"
        assert "At least one symbol is required" in result["result"]["error"]

    @pytest.mark.exception_test
    @pytest.mark.skip(reason="Slow exception test - run with pytest -m exception_test")
    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_remove_symbols_from_watchlist_exception(
        self, mock_execute_with_retry: Any
    ) -> None:
        """Test exception handling during symbol removal."""
        mock_execute_with_retry.side_effect = Exception("API Error")

        result = await remove_symbols_from_watchlist("Tech Stocks", ["AAPL"])

        assert "result" in result
        assert result["result"]["success"] is False
        assert result["result"]["status"] == "error"
        assert "Error removing symbols" in result["result"]["error"]


class TestGetWatchlistPerformance:
    """Test get watchlist performance functionality."""

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.get_watchlist_by_name")
    @pytest.mark.slow
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_performance_success(
        self, mock_get_watchlist: Any
    ) -> None:
        """Test watchlist performance handling when API calls fail."""
        # Mock watchlist data
        mock_get_watchlist.return_value = {
            "result": {
                "name": "Tech Stocks",
                "symbols": ["AAPL", "GOOGL"],
                "status": "success",
            }
        }

        # Due to source code bug (rh.get_quote doesn't exist), the function will fail
        # Test that it handles this gracefully by returning error data
        result = await get_watchlist_performance("Tech Stocks")

        assert "result" in result
        assert result["result"]["watchlist_name"] == "Tech Stocks"
        assert len(result["result"]["symbols"]) == 2

        # Check that symbols are returned but with error data
        for symbol_data in result["result"]["symbols"]:
            assert symbol_data["symbol"] in ["AAPL", "GOOGL"]
            assert symbol_data["current_price"] == "N/A"
            assert symbol_data["change"] == "N/A"
            assert symbol_data["change_percent"] == "N/A"
            assert "error" in symbol_data

        # Check summary - no gainers/losers since all failed
        summary = result["result"]["summary"]
        assert summary["total_symbols"] == 2
        assert summary["gainers"] == 0
        assert summary["losers"] == 0
        assert summary["unchanged"] == 0
        assert summary["total_volume"] == 0
        assert result["result"]["status"] == "success"

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.get_watchlist_by_name")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_performance_watchlist_not_found(
        self, mock_get_watchlist: Any
    ) -> None:
        """Test watchlist performance when watchlist not found."""
        mock_get_watchlist.return_value = {
            "result": {"status": "not_found", "message": "Watchlist not found"}
        }

        result = await get_watchlist_performance("NonExistent")

        assert "result" in result
        assert result["result"]["status"] == "not_found"

    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_performance_empty_name(self) -> None:
        """Test watchlist performance with empty name."""
        result = await get_watchlist_performance("")

        assert "result" in result
        assert result["result"]["status"] == "error"
        assert "Watchlist name is required" in result["result"]["error"]

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.get_watchlist_by_name")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_performance_empty_watchlist(
        self, mock_get_watchlist: Any
    ) -> None:
        """Test watchlist performance with no symbols."""
        mock_get_watchlist.return_value = {
            "result": {"name": "Empty", "symbols": [], "status": "success"}
        }

        result = await get_watchlist_performance("Empty")

        assert "result" in result
        assert result["result"]["watchlist_name"] == "Empty"
        assert result["result"]["symbols"] == []
        assert result["result"]["summary"]["total_symbols"] == 0
        assert result["result"]["status"] == "no_data"

    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.get_watchlist_by_name")
    @pytest.mark.slow
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_performance_mixed_results(
        self, mock_get_watchlist: Any
    ) -> None:
        """Test watchlist performance with multiple symbols (all will fail gracefully)."""
        mock_get_watchlist.return_value = {
            "result": {
                "name": "Mixed",
                "symbols": ["AAPL", "TSLA", "UNCHANGED"],
                "status": "success",
            }
        }

        # Due to source code bug (rh.get_quote doesn't exist), all symbols will fail
        # Test that it handles this gracefully
        result = await get_watchlist_performance("Mixed")

        assert "result" in result
        summary = result["result"]["summary"]
        assert summary["total_symbols"] == 3
        # All will fail so no gainers/losers/unchanged
        assert summary["gainers"] == 0
        assert summary["losers"] == 0
        assert summary["unchanged"] == 0
        assert summary["total_volume"] == 0

    @pytest.mark.exception_test
    @pytest.mark.skip(reason="Slow exception test - run with pytest -m exception_test")
    @patch("open_stocks_mcp.tools.robinhood_watchlist_tools.get_watchlist_by_name")
    @pytest.mark.journey_watchlists
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_get_watchlist_performance_api_error(
        self, mock_get_watchlist: Any
    ) -> None:
        """Test watchlist performance when API calls fail for some symbols."""
        mock_get_watchlist.return_value = {
            "result": {"name": "Test", "symbols": ["AAPL"], "status": "success"}
        }

        # Mock API error
        with patch(
            "open_stocks_mcp.tools.robinhood_watchlist_tools.execute_with_retry"
        ) as mock_execute:
            mock_execute.side_effect = Exception("API Error")

            result = await get_watchlist_performance("Test")

            assert "result" in result
            assert len(result["result"]["symbols"]) == 1
            symbol_data = result["result"]["symbols"][0]
            assert symbol_data["symbol"] == "AAPL"
            assert symbol_data["current_price"] == "N/A"
            assert symbol_data["change"] == "N/A"
            assert "error" in symbol_data
            assert result["result"]["status"] == "success"
