# SPDX-License-Identifier: LGPL-2.1-or-later
# Copyright (c) 2025 DBZero Software sp. z o.o.

import pytest
import dbzero as db0
from .conftest import DB0_DIR
from dbzero import memo
from dataclasses import dataclass, field


@memo
class BaseClass:
    def __init__(self, base_value):
        self.base_value = base_value

    def base_method(self):
        return self.base_value        
    
    def overloaded_method(self):
        return self.base_value * 2

@memo
class DerivedClass(BaseClass):
    def __init__(self, base_value, value):
        super().__init__(base_value)
        self.value = value

    def overloaded_method(self):
        return super().overloaded_method() + self.value        

class HasColor:
    def __init__(self, color):
        self.color = color

@memo
class MemoWithColor(HasColor):
    def __init__(self, amount, color):        
        self.amount = amount
        super().__init__(color)


@memo
class MultipleDerivedClass(DerivedClass, HasColor):
    def __init__(self, base_value, value, color):
        super().__init__(base_value, value)
        HasColor.__init__(self, color)
    
    
def test_derived_instance_is_created(db0_fixture):
    derived = DerivedClass(1, 2)
    # assign tag to persist the instance
    db0.tags(derived).add("temp")
    assert derived.base_value == 1
    assert derived.value == 2
    id = db0.uuid(derived)
    prefix_name = db0.get_prefix_of(derived).name
    # close db0 and open as read-only
    db0.commit()
    db0.close()
    
    db0.init(DB0_DIR)
    db0.open(prefix_name, "r")    
    object_1 = db0.fetch(id)
    assert object_1.base_value == 1
    assert object_1.value == 2


def test_call_method_from_base_class(db0_fixture):
    derived = DerivedClass(7, 2)
    assert derived.base_method() == 7
    
    
def test_child_class_eq_issue_1(db0_fixture):
    """
    Issue related with the following ticket:
    https://github.com/wskozlowski/dbzero/issues/232
    """
    a = DerivedClass(1, 2)
    assert a is a
    assert a == a


def test_memo_derived_from_regular_class(db0_fixture):
    obj = MemoWithColor(100, "red")
    assert obj.amount == 100
    assert obj.color == "red"


def test_destroying_derived_objects_when_untagged(db0_fixture):
    db0.tags(DerivedClass(0, 0)).add("tag-1")
    uuid = db0.uuid(next(iter(db0.find(DerivedClass, "tag-1"))))
    db0.commit()
    assert db0.exists(uuid)
    db0.tags(next(iter(db0.find(DerivedClass, "tag-1")))).remove("tag-1")
    db0.commit()
    assert not db0.exists(uuid)


def test_destroying_objects_with_multiple_bases_when_untagged(db0_fixture):
    db0.tags(MultipleDerivedClass(0, 0, color = "red")).add("tag-1")
    # NOTE: here we find by one of the bases
    uuid = db0.uuid(next(iter(db0.find(DerivedClass, "tag-1"))))
    db0.commit()
    assert db0.exists(uuid)
    db0.tags(next(iter(db0.find(DerivedClass, "tag-1")))).remove("tag-1")
    db0.commit()
    assert not db0.exists(uuid)


def test_issubclass_for_memo_types(db0_fixture):
    assert issubclass(DerivedClass, BaseClass)
    assert issubclass(MemoWithColor, HasColor)
    assert issubclass(MultipleDerivedClass, DerivedClass)
    assert issubclass(MultipleDerivedClass, HasColor)
    
    
class BaseClassWithInitSubclass:
    __last_subclass = None
    
    def __init_subclass__(cls, **kwargs):
        """
        __init_subclass__ is called twice since @memo adds another subclassing layer.
        """
        BaseClassWithInitSubclass.__last_subclass = cls
    
    @classmethod
    def make_instance(cls, *args, **kwargs):
        return cls.__last_subclass(*args, **kwargs)
    
@db0.memo
class MemoWithInitSubclass(BaseClassWithInitSubclass):
    pass
    
def test_init_subclass_with_memo(db0_fixture):
    obj = BaseClassWithInitSubclass.make_instance()
    print(f"obj: {type(obj)}")
    assert isinstance(obj, MemoWithInitSubclass) 
    assert db0.is_memo(obj)


def test_memo_setattr_derived_from_python_type(db0_fixture):
    obj = MemoWithInitSubclass()
    obj.__setattr__('new_attr', 123)
    assert obj.new_attr == 123


def test_memo_mro_derived_class(db0_fixture):
    obj = DerivedClass(1, 2)
    assert obj.overloaded_method() == 4
    

def test_memo_mro_derived_class_fetched_as_base_type(db0_fixture):
    obj = DerivedClass(1, 2)
    px_name = db0.get_prefix_of(obj).name
    db0.tags(obj).add("temp")
    uuid = db0.uuid(obj)
    db0.close()
    db0.init(DB0_DIR)
    db0.open(px_name, "r")
    # fetch as base type
    obj_2 = db0.fetch(DerivedClass, uuid)
    assert obj_2.overloaded_method() == 4
    
    
def test_memo_mro_derived_class_found_as_base_type(db0_fixture):
    obj = DerivedClass(1, 2)
    px_name = db0.get_prefix_of(obj).name
    db0.tags(obj).add("temp")
    db0.close()
    db0.init(DB0_DIR)
    db0.open(px_name, "r")
    # fetch as base type
    obj_2 = next(iter(db0.find(BaseClass)))
    assert obj_2.overloaded_method() == 4