import shutil
from dataclasses import dataclass

import pytest

from myfasthtml.core.dbmanager import DbManager, DbObject
from myfasthtml.core.instances import SingleInstance, BaseInstance


@pytest.fixture(scope="session")
def session():
  return {
      "user_info": {
          "id": "test_tenant_id",
          "email": "test@email.com",
          "username": "test user",
          "role": [],
      }
  }


@pytest.fixture
def parent(session):
  return SingleInstance(session=session, _id="test_parent_id")


@pytest.fixture
def db_manager(parent):
  shutil.rmtree("TestDb", ignore_errors=True)
  db_manager_instance = DbManager(parent, root="TestDb", auto_register=False)
  
  yield db_manager_instance
  
  shutil.rmtree("TestDb", ignore_errors=True)


def simplify(res: dict) -> dict:
  return {k: v for k, v in res.items() if not k.startswith("_")}


def test_i_can_init(parent, db_manager):
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
      
      with self.initializing():
        self.value: str = "hello"
        self.number: int = 42
        self.none_value: None = None
  
  dummy = DummyObject(parent)
  
  props = dummy._get_properties()
  
  in_db = db_manager.load("DummyObject")
  history = db_manager.db.history(db_manager.get_tenant(), "DummyObject")
  assert simplify(in_db) == {"value": "hello", "number": 42, "none_value": None}
  assert len(history) == 1


def test_i_can_init_from_dataclass(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    value: str = "hello"
    number: int = 42
    none_value: None = None
  
  DummyObject(parent)
  
  in_db = db_manager.load("DummyObject")
  history = db_manager.db.history(db_manager.get_tenant(), "DummyObject")
  assert simplify(in_db) == {"value": "hello", "number": 42, "none_value": None}
  assert len(history) == 1


def test_i_can_init_from_db_with(parent, db_manager):
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
      
      with self.initializing():
        self.value: str = "hello"
        self.number: int = 42
  
  # insert other values in db
  db_manager.save("DummyObject", {"value": "other_value", "number": 34})
  
  dummy = DummyObject(parent)
  
  assert dummy.value == "other_value"
  assert dummy.number == 34


def test_i_can_init_from_db_with_dataclass(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    value: str = "hello"
    number: int = 42
  
  # insert other values in db
  db_manager.save("DummyObject", {"value": "other_value", "number": 34})
  
  dummy = DummyObject(parent)
  
  assert dummy.value == "other_value"
  assert dummy.number == 34


def test_i_do_not_save_when_prefixed_by_underscore_or_ns(parent, db_manager):
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
      
      with self.initializing():
        self.to_save: str = "value"
        self._not_to_save: str = "value"
        self.ns_not_to_save: str = "value"
    
    to_save: str = "value"
    _not_to_save: str = "value"
    ns_not_to_save: str = "value"
  
  dummy = DummyObject(parent)
  dummy.to_save = "other_value"
  dummy.ns_not_to_save = "other_value"
  dummy._not_to_save = "other_value"
  
  in_db = db_manager.load("DummyObject")
  assert in_db["to_save"] == "other_value"
  assert "_not_to_save" not in in_db
  assert "ns_not_to_save" not in in_db


def test_i_do_not_save_when_prefixed_by_underscore_or_ns_with_dataclass(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    to_save: str = "value"
    _not_to_save: str = "value"
    ns_not_to_save: str = "value"
  
  dummy = DummyObject(parent)
  dummy.to_save = "other_value"
  dummy.ns_not_to_save = "other_value"
  dummy._not_to_save = "other_value"
  
  in_db = db_manager.load("DummyObject")
  assert in_db["to_save"] == "other_value"
  assert "_not_to_save" not in in_db
  assert "ns_not_to_save" not in in_db


def test_db_is_updated_when_attribute_is_modified(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    value: str = "hello"
    number: int = 42
  
  dummy = DummyObject(parent)
  dummy.value = "other_value"
  
  assert simplify(db_manager.load("DummyObject")) == {"value": "other_value", "number": 42}


def test_i_do_not_save_in_db_when_value_is_the_same(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    value: str = "hello"
    number: int = 42
  
  dummy = DummyObject(parent)
  dummy.value = "other_value"
  in_db_1 = db_manager.load("DummyObject")
  
  dummy.value = "other_value"
  in_db_2 = db_manager.load("DummyObject")
  
  assert in_db_1["__parent__"] == in_db_2["__parent__"]


def test_i_can_update(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    value: str = "hello"
    number: int = 42
  
  dummy = DummyObject(parent)
  clone = dummy.copy()
  
  clone.number = 34
  clone.value = "other_value"
  clone.other_attr = "some_value"
  
  dummy.update(clone)
  
  assert simplify(db_manager.load("DummyObject")) == {"value": "other_value", "number": 34}


def test_forbidden_attributes_are_not_the_copy(parent, db_manager):
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
      
      with self.initializing():
        self.value: str = "hello"
        self.number: int = 42
        self.none_value: None = None
  
  dummy = DummyObject(parent)
  clone = dummy.copy()
  
  for k in DbObject._forbidden_attrs:
    assert not hasattr(clone, k), f"Clone should not have forbidden attribute '{k}'"


def test_forbidden_attributes_are_not_the_copy_for_dataclass(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    value: str = "hello"
    number: int = 42
    none_value: None = None
  
  dummy = DummyObject(parent)
  clone = dummy.copy()
  
  for k in DbObject._forbidden_attrs:
    assert not hasattr(clone, k), f"Clone should not have forbidden attribute '{k}'"


def test_i_cannot_update_a_forbidden_attribute(parent, db_manager):
  @dataclass
  class DummyObject(DbObject):
    def __init__(self, owner: BaseInstance):
      super().__init__(owner, "DummyObject", db_manager)
    
    value: str = "hello"
    number: int = 42
    none_value: None = None
  
  dummy = DummyObject(parent)
  
  dummy.update(_owner="other_value")
  
  assert dummy._owner is parent
