import io

import pytest

from storage import (
    Storage,
    json_dump_storage,
    json_dumps_storage,
    json_load_storage,
    json_loads_storage,
    storageify,
    unstorageify,
    unicode_storage,
)


class TestStorage:
    def test_init_empty(self):
        s = Storage()
        assert len(s) == 0

    def test_init_kwargs(self):
        s = Storage(a=1, b=2)
        assert s.a == 1
        assert s.b == 2

    def test_init_dict(self):
        d = {"a": 1, "b": 2}
        s = Storage(dict_=d)
        assert s.a == 1
        assert s.b == 2
        assert s.get_contained_dict() is d

    def test_init_dict_shared(self):
        d = {"a": 1}
        s = Storage(dict_=d)
        s.b = 2
        assert d == {"a": 1, "b": 2}

    def test_init_default_factory(self):
        s = Storage(default_factory=list)
        assert s.a == []
        assert s.b == []

    def test_get_contained_dict(self):
        d = {"a": 1}
        s = Storage(dict_=d)
        assert s.get_contained_dict() is d

    def test_get_dict(self):
        s = Storage(a=1, b=2)
        assert s.get_dict() == {"a": 1, "b": 2}

    def test_items(self):
        s = Storage(a=1, b=2)
        items = sorted(s.items())
        assert items == [("a", 1), ("b", 2)]

    def test_iter(self):
        s = Storage(a=1, b=2)
        keys = sorted(list(s))
        assert keys == ["a", "b"]

    def test_contains(self):
        s = Storage(a=1)
        assert "a" in s
        assert "b" not in s

    def test_len(self):
        s = Storage(a=1, b=2, c=3)
        assert len(s) == 3

    def test_getattr(self):
        s = Storage(a=1)
        assert s.a == 1

    def test_getattr_missing(self):
        s = Storage(a=1)
        with pytest.raises(AttributeError):
            _ = s.b

    def test_setattr(self):
        s = Storage()
        s.a = 1
        assert s.a == 1
        assert s["a"] == 1

    def test_setattr_internal(self):
        s = Storage()
        s._dict = {"a": 1}
        assert s._dict == {"a": 1}

    def test_delattr(self):
        s = Storage(a=1)
        del s.a
        assert "a" not in s

    def test_delattr_missing(self):
        s = Storage(a=1)
        with pytest.raises(AttributeError):
            del s.b

    def test_getitem(self):
        s = Storage(a=1)
        assert s["a"] == 1

    def test_getitem_missing(self):
        s = Storage(a=1)
        with pytest.raises(KeyError):
            _ = s["b"]

    def test_setitem(self):
        s = Storage()
        s["a"] = 1
        assert s.a == 1

    def test_delitem(self):
        s = Storage(a=1)
        del s["a"]
        assert "a" not in s

    def test_eq(self):
        s1 = Storage(a=1, b=2)
        s2 = Storage(b=2, a=1)
        assert s1 == s2

    def test_eq_not_equal(self):
        s1 = Storage(a=1, b=2)
        s2 = Storage(a=1, b=3)
        assert s1 != s2

    def test_eq_non_storage(self):
        s = Storage(a=1)
        assert s != {"a": 1}

    def test_repr(self):
        s = Storage(a=1, b=2)
        r = repr(s)
        assert "Storage" in r
        assert "a=1" in r
        assert "b=2" in r


class TestCaseInsensitive:
    def test_getattr(self):
        s = Storage(a=1, case_insensitive=True)
        assert s.A == 1

    def test_setattr(self):
        s = Storage(case_insensitive=True)
        s.A = 1
        assert s.a == 1

    def test_getitem(self):
        s = Storage(a=1, case_insensitive=True)
        assert s["A"] == 1

    def test_setitem(self):
        s = Storage(case_insensitive=True)
        s["A"] = 1
        assert s["a"] == 1

    def test_contains(self):
        s = Storage(a=1, case_insensitive=True)
        assert "A" in s


class TestNormalizeDenormalize:
    def test_normalize(self):
        s = Storage(a=1, normalize=str.upper)
        assert s["A"] == 1

    def test_denormalize(self):
        s = Storage(a=1, denormalize=str.upper)
        keys = list(s)
        assert "A" in keys

    def test_normalize_and_denormalize(self):
        s = Storage(a=1, normalize=str.upper, denormalize=str.lower)
        assert s["A"] == 1
        keys = list(s)
        assert "a" in keys


class TestJson:
    def test_json_loads_storage(self):
        result = json_loads_storage('[{"a": 1}]')
        assert len(result) == 1
        assert result[0].a == 1

    def test_json_load_storage(self):
        fp = io.StringIO('[{"a": 1}]')
        result = json_load_storage(fp)
        assert len(result) == 1
        assert result[0].a == 1

    def test_json_dumps_storage(self):
        s = Storage(a=1)
        result = json_dumps_storage(s)
        assert '"a": 1' in result

    def test_json_dumps_storage_list(self):
        s = [Storage(a=1), Storage(b=2)]
        result = json_dumps_storage(s)
        assert "a" in result
        assert "b" in result

    def test_json_dumps_storage_dict(self):
        d = {"a": 1}
        result = json_dumps_storage(d)
        assert '"a": 1' in result

    def test_json_dump_storage(self):
        s = Storage(a=1)
        fp = io.StringIO()
        json_dump_storage(s, fp)
        assert '"a": 1' in fp.getvalue()


class TestStorageify:
    def test_storageify_dict(self):
        result = storageify({"a": 1})
        assert isinstance(result, Storage)
        assert result.a == 1

    def test_storageify_nested(self):
        result = storageify({"a": {"b": 1}})
        assert isinstance(result, Storage)
        assert isinstance(result.a, Storage)
        assert result.a.b == 1

    def test_storageify_list(self):
        result = storageify([{"a": 1}, {"b": 2}])
        assert len(result) == 2
        assert result[0].a == 1
        assert result[1].b == 2

    def test_storageify_primitive(self):
        assert storageify(1) == 1
        assert storageify("test") == "test"

    def test_storageify_custom_factory(self):
        class CustomStorage(Storage):
            pass

        result = storageify({"a": 1}, storageFactory=CustomStorage)
        assert isinstance(result, CustomStorage)


class TestUnstorageify:
    def test_unstorageify_storage(self):
        s = Storage(a=1, b=2)
        result = unstorageify(s)
        assert result == {"a": 1, "b": 2}

    def test_unstorageify_nested(self):
        s = Storage(a=Storage(b=1))
        result = unstorageify(s)
        assert result == {"a": {"b": 1}}

    def test_unstorageify_list(self):
        s = [Storage(a=1), Storage(b=2)]
        result = unstorageify(s)
        assert result == [{"a": 1}, {"b": 2}]

    def test_unstorageify_dict(self):
        d = {"a": 1, "b": 2}
        result = unstorageify(d)
        assert result == {"a": 1, "b": 2}

    def test_unstorageify_primitive(self):
        assert unstorageify(1) == 1
        assert unstorageify("test") == "test"


class TestUnicodeStorage:
    def test_unicode_storage(self):
        s = Storage(a="b", b=1)
        result = unicode_storage(s)
        assert result["a"] == "b"
        assert result["b"] == "1"
