"""Tests for the DeckGL class."""

import json
from pathlib import Path
from unittest.mock import MagicMock, patch

import param
import pytest

from holoviz_utils.deckgl import DeckGL


def test_deckgl_initialization_defaults():
    """Test DeckGL initialization with default parameters."""
    deck = DeckGL()

    # Check that the instance was created
    assert deck is not None

    # Check default parameter values
    assert deck.object != ""  # object gets set by update_object in __init__
    assert deck.initial_view_state is None  # Default is None, not {}
    assert deck.layers == []
    assert deck.pickable is None
    assert deck.click_state is None


def test_deckgl_initialization_with_params():
    """Test DeckGL initialization with custom parameters."""
    initial_view_state = {
        "longitude": -122.4,
        "latitude": 37.8,
        "zoom": 12,
        "pitch": 0,
        "bearing": 0
    }

    layers = [
        {
            "@@type": "ScatterplotLayer",
            "data": [{"position": [-122.4, 37.8]}],
            "getPosition": "@@=d.position",
            "getRadius": 100,
            "getFillColor": [255, 0, 0]
        }
    ]

    deck = DeckGL(
        initial_view_state=initial_view_state,
        layers=layers
    )

    # Check that parameters were set correctly
    assert deck.initial_view_state == initial_view_state
    assert deck.layers == layers


def test_deckgl_parameter_types():
    """Test that DeckGL parameters have correct types."""
    deck = DeckGL()

    # Check parameter types
    assert isinstance(deck.param.object, param.String)
    assert isinstance(deck.param.initial_view_state, param.Dict)
    assert isinstance(deck.param.layers, param.List)
    assert isinstance(deck.param.pickable, param.Dict)
    assert isinstance(deck.param.click_state, param.Dict)


def test_deckgl_esm_attribute():
    """Test that _esm attribute is set correctly."""
    deck = DeckGL()

    # Check that _esm is a Path object pointing to deckgl.js
    assert deck._esm == Path("deckgl.js")

    # Check that the deckgl.js file exists in the same directory as the class
    import holoviz_utils.deckgl
    module_dir = Path(holoviz_utils.deckgl.__file__).parent
    esm_file = module_dir / deck._esm
    assert esm_file.exists(), f"Expected ESM file not found at {esm_file}"


def test_deckgl_update_object():
    """Test that update_object method generates correct JSON."""
    initial_view_state = {"longitude": -122.4, "latitude": 37.8, "zoom": 12}
    layers = [{"@@type": "ScatterplotLayer", "id": "layer1"}]

    deck = DeckGL(initial_view_state=initial_view_state, layers=layers)

    # Parse the object JSON
    obj = json.loads(deck.object)

    # Check that the JSON contains expected keys
    assert "initialViewState" in obj
    assert "layers" in obj
    assert "controller" in obj
    assert "onClick" in obj
    assert "getTooltip" in obj

    # Check values
    assert obj["initialViewState"] == initial_view_state
    assert obj["layers"] == layers
    assert obj["controller"] is True
    assert obj["onClick"] == {"@@function": "set_clicker"}
    assert obj["getTooltip"] == {"@@function": "set_tooltip"}


def test_deckgl_update_object_on_initial_view_state_change():
    """Test that object is updated when initial_view_state changes."""
    deck = DeckGL()

    # Get initial object value
    initial_object = deck.object

    # Change initial_view_state
    new_view_state = {"longitude": -122.4, "latitude": 37.8, "zoom": 10}
    deck.initial_view_state = new_view_state

    # Check that object was updated
    assert deck.object != initial_object

    # Parse and verify new object
    obj = json.loads(deck.object)
    assert obj["initialViewState"] == new_view_state


def test_deckgl_update_object_on_layers_change():
    """Test that object is updated when layers change."""
    deck = DeckGL()

    # Get initial object value
    initial_object = deck.object

    # Change layers
    new_layers = [{"@@type": "ScatterplotLayer", "id": "test-layer"}]
    deck.layers = new_layers

    # Check that object was updated
    assert deck.object != initial_object

    # Parse and verify new object
    obj = json.loads(deck.object)
    assert obj["layers"] == new_layers


def test_deckgl_handle_msg():
    """Test that _handle_msg correctly updates parameters."""
    deck = DeckGL()

    # Create a message with click_state update
    msg_data = {
        "click_state": {
            "x": 100,
            "y": 200,
            "coordinate": [-122.4, 37.8]
        }
    }
    msg = json.dumps(msg_data)

    # Handle the message
    deck._handle_msg(msg)

    # Check that click_state was updated
    assert deck.click_state == msg_data["click_state"]


def test_deckgl_handle_msg_multiple_params():
    """Test that _handle_msg can update multiple parameters."""
    deck = DeckGL()

    # Create a message with multiple parameter updates
    msg_data = {
        "click_state": {"x": 100, "y": 200},
        "pickable": {"layer": "test-layer", "index": 5}
    }
    msg = json.dumps(msg_data)

    # Handle the message
    deck._handle_msg(msg)

    # Check that both parameters were updated
    assert deck.click_state == msg_data["click_state"]
    assert deck.pickable == msg_data["pickable"]


@patch.object(DeckGL, '_send_msg')
def test_deckgl_send_object_to_js(mock_send_msg):
    """Test that send_object_to_js sends the correct message."""
    deck = DeckGL()

    # Trigger send_object_to_js by updating the object parameter
    deck.object = '{"test": "data"}'

    # Check that _send_msg was called with correct argument
    mock_send_msg.assert_called()
    call_args = mock_send_msg.call_args[0][0]
    assert "object" in call_args
    assert call_args["object"] == '{"test": "data"}'


def test_deckgl_pickable_allows_none():
    """Test that pickable parameter allows None values."""
    deck = DeckGL(pickable=None)
    assert deck.pickable is None

    # Set to a dict
    deck.pickable = {"test": "value"}
    assert deck.pickable == {"test": "value"}

    # Set back to None
    deck.pickable = None
    assert deck.pickable is None


def test_deckgl_complex_layers():
    """Test DeckGL with complex layer configurations."""
    layers = [
        {
            "@@type": "ScatterplotLayer",
            "id": "scatterplot-layer",
            "data": [
                {"position": [-122.4, 37.8], "size": 100},
                {"position": [-122.5, 37.9], "size": 200}
            ],
            "getPosition": "@@=d.position",
            "getRadius": "@@=d.size",
            "getFillColor": [255, 0, 0, 200]
        },
        {
            "@@type": "ArcLayer",
            "id": "arc-layer",
            "data": [
                {"source": [-122.4, 37.8], "target": [-122.5, 37.9]}
            ],
            "getSourcePosition": "@@=d.source",
            "getTargetPosition": "@@=d.target",
            "getSourceColor": [0, 128, 200],
            "getTargetColor": [200, 0, 80]
        }
    ]

    deck = DeckGL(layers=layers)

    # Parse the object
    obj = json.loads(deck.object)

    # Check that all layers are preserved
    assert len(obj["layers"]) == 2
    assert obj["layers"][0]["@@type"] == "ScatterplotLayer"
    assert obj["layers"][1]["@@type"] == "ArcLayer"
    assert obj["layers"][0]["id"] == "scatterplot-layer"
    assert obj["layers"][1]["id"] == "arc-layer"
