from dataclasses import dataclass
from typing import Any

import pytest
from fasthtml.components import Button, Div
from myutils.observable import make_observable, bind

from myfasthtml.core.commands import Command, CommandsManager
from myfasthtml.core.constants import ROUTE_ROOT, Routes
from myfasthtml.test.matcher import matches


@dataclass
class Data:
  value: Any


def callback():
  return "Hello World"


@pytest.fixture(autouse=True)
def reset_command_manager():
  CommandsManager.reset()


def test_i_can_create_a_command_with_no_params():
  command = Command('test', 'Command description', callback)
  assert command.id is not None
  assert command.name == 'test'
  assert command.description == 'Command description'
  assert command.execute() == "Hello World"


def test_command_are_registered():
  command = Command('test', 'Command description', callback)
  assert CommandsManager.commands.get(str(command.id)) is command


def test_i_can_bind_a_command_to_an_element():
  command = Command('test', 'Command description', callback)
  elt = Button()
  updated = command.bind_ft(elt)
  
  expected = Button(hx_post=f"{ROUTE_ROOT}{Routes.Commands}")
  
  assert matches(updated, expected)


def test_i_can_suppress_swapping_with_target_attr():
  command = Command('test', 'Command description', callback).htmx(target=None)
  elt = Button()
  updated = command.bind_ft(elt)
  
  expected = Button(hx_post=f"{ROUTE_ROOT}{Routes.Commands}", hx_swap="none")
  
  assert matches(updated, expected)


def test_i_can_bind_a_command_to_an_observable():
  data = Data("hello")
  
  def on_data_change(old, new):
    return old, new
  
  def another_callback():
    data.value = "new value"
    return "another callback result"
  
  make_observable(data)
  bind(data, "value", on_data_change)
  command = Command('test', 'Command description', another_callback).bind(data)
  
  res = command.execute()
  
  assert res == ["another callback result", ("hello", "new value")]


def test_i_can_bind_a_command_to_an_observable_2():
  data = Data("hello")
  
  def on_data_change(old, new):
    return old, new
  
  def another_callback():
    data.value = "new value"
    return ["another 1", "another 2"]
  
  make_observable(data)
  bind(data, "value", on_data_change)
  command = Command('test', 'Command description', another_callback).bind(data)
  
  res = command.execute()
  
  assert res == ["another 1", "another 2", ("hello", "new value")]


def test_by_default_swap_is_set_to_outer_html():
  command = Command('test', 'Command description', callback)
  elt = Button()
  updated = command.bind_ft(elt)
  
  expected = Button(hx_post=f"{ROUTE_ROOT}{Routes.Commands}", hx_swap="outerHTML")
  
  assert matches(updated, expected)


@pytest.mark.parametrize("return_values", [
    [Div(), Div(), "hello", Div()],  # list
    (Div(), Div(), "hello", Div())  # tuple
])
def test_swap_oob_is_automatically_set_when_multiple_elements_are_returned(return_values):
  """Test that hx-swap-oob is automatically set, but not for the first."""
  
  def another_callback():
    return return_values
  
  command = Command('test', 'Command description', another_callback)
  
  res = command.execute()
  
  assert "hx_swap_oob" not in res[0].attrs
  assert res[1].attrs["hx-swap-oob"] == "true"
  assert res[3].attrs["hx-swap-oob"] == "true"
