import pytest
from . import sample_models
import datetime
import base64


def test_collection_find(Sample):

    Sample.insert_one({"name": "XXX", "url": "http://example.com"})

    _id = Sample.find_one()["_id"]

    # Don't include _id by default
    assert dict(Sample.find_one({"name": "XXX"})) == {"name": "XXX", "url": "http://example.com", "_id": _id}
    assert dict(Sample.find_one({"name": "XXX"}, fields=["name"])) == {"name": "XXX"}
    assert dict(Sample.find_one({"name": "XXX"}, fields={"_id": 1, "name": 1})) == {"name": "XXX", "_id": _id}
    assert dict(Sample.find_one({"name": "XXX"}, fields={"name": 1})) == {"name": "XXX"}
    assert dict(Sample.find_one({"name": "XXX"}, fields={"name": True})) == {"name": "XXX"}
    assert dict(Sample.find_one({"name": "XXX"}, fields=set(["name"]))) == {"name": "XXX"}
    assert dict(Sample.find_one({"name": "XXX"}, fields=frozenset(["name"]))) == {"name": "XXX"}
    assert dict(Sample.find_one({"name": "XXX"}, fields=("name", ))) == {"name": "XXX"}


def test_b64ids(Sample):

    Sample.insert_one({"name": "XXX", "url": "http://example.com"})

    s = Sample.find_one()

    assert s.b64id == base64.b64encode(s["_id"].binary)
    assert Sample.find_by_b64id(s.b64id)["_id"] == s["_id"]
    assert list(Sample.find_by_b64ids([s.b64id]))[0]["_id"] == s["_id"]


def test_document_no_shared_fields(Sample):

    new_object = Sample({"name": "XXX", "url": "http://example.com"})
    new_object.save()

    cursor1 = Sample.find({"_id": new_object["_id"]}, fields={"_id": True, "url": True})
    cursor2 = Sample.find({"_id": new_object["_id"]}, fields={"_id": True, "name": True})

    obj1 = list(cursor1)
    obj2 = list(cursor2)

    assert "url" in obj1[0]
    assert "name" not in obj1[0]

    assert "url" not in obj2[0]
    assert "name" in obj2[0]


def test_document_reload_if_unset_field(Sample):

    store = Sample({"name": "ab", "url": "mongokit.com"})
    store.save()
    _id = store["_id"]

    # The same
    store2 = Sample.find_one()
    store2["url"] = "x"

    store.unset_fields(["name"])
    store.reload()
    assert store.get("name") is None
    assert store["_id"] == _id
    assert store["url"] == "mongokit.com"

    # This may happen in a different context
    store2.reload()
    assert store2.get("name") is None
    assert store2["_id"] == _id
    assert store2["url"] == "mongokit.com"


def test_document_delete(Sample):

    Sample.insert({"a": 1})

    s = Sample.find_one()
    assert s
    s.delete()

    s = Sample.find_one()
    assert s is None


def test_document_common_methods(Sample):

  from bson import ObjectId
  import collections

  assert Sample.collection.find().count() == 0

  # Instanciate
  new_object = Sample({"name": "XXX", "url": "http://example.com"})

  # Should not save to DB yet.
  assert Sample.collection.find().count() == 0

  # Now save()
  new_object.save()

  # Once the object is in DB, we can't do it anymore.
  with pytest.raises(Exception):
    new_object.save()

  assert type(new_object["_id"]) == ObjectId

  assert Sample.collection.find().count() == 1
  db_object = Sample.collection.find_one()
  assert type(db_object) == dict
  assert db_object["name"] == "XXX"

  # test insert()
  inserted_object = Sample.insert({"name": "ZZZ", "url": "http://example2.com", "stats": {"nb_of_products": 2}})
  assert type(inserted_object) == ObjectId

  assert Sample.collection.find().count() == 2

  # Find back with different methods
  orm_object = Sample.find_by_id(db_object["_id"])
  assert orm_object["name"] == "XXX"
  orm_object = Sample.find_by_id(str(db_object["_id"]))
  assert orm_object["name"] == "XXX"
  orm_object = Sample.find_by_id({"_id": db_object["_id"]})
  assert orm_object["name"] == "XXX"
  orm_object = Sample.find_by_id({"_id": str(db_object["_id"])})
  assert orm_object["name"] == "XXX"
  assert isinstance(orm_object, sample_models.SampleDocument)

  # exists()
  assert Sample.exists({"name": "XXX"})

  # Other find styles
  cursor = Sample.find({"name": "XXX"})
  assert "cursor" in str(type(cursor)).lower()
  orm_objects = list(cursor)
  assert len(orm_objects) == 1
  assert isinstance(orm_objects[0], sample_models.SampleDocument)
  assert orm_objects[0]["name"] == "XXX"

  orm_object = Sample.find_one({"_id": db_object["_id"]})
  assert orm_object["name"] == "XXX"
  assert isinstance(orm_object, sample_models.SampleDocument)

  # TODO - should that not work?
  orm_object = Sample.find_one({"_id": str(db_object["_id"])})
  assert orm_object is None

  col_cursor = Sample.iter_column({"name": "XXX"})
  assert isinstance(col_cursor, collections.Iterable)
  assert list(col_cursor) == [new_object["_id"]]

  col = Sample.list_column({"name": "XXX"}, field="name")
  assert col == ["XXX"]
  col = Sample.list_column({"name": "ZZZ"}, field="stats.nb_of_products")
  assert col == [2]

  with pytest.raises(KeyError):
    Sample.list_column({"name": "ZZZ"}, field="inexistent_field")

  # We should be able to fetch & save partial objects.
  orm_object = Sample.find_by_id(db_object["_id"], fields=["url"])
  assert list(dict(orm_object).keys()) == ["url"]
  assert dict(orm_object)["url"] == "http://example.com"

  # If we save() that, it will create a new object because we lack an _id :(
  with pytest.raises(Exception):
    orm_object.save()

  assert Sample.collection.find().count() == 2

  # FIXME not anymore as we are requesting _id for each query
  # orm_object.save(force=True)

  # assert Sample.collection.find().count() == 3

  orm_object = Sample.find_by_id(db_object["_id"], fields=["url", "_id"])
  assert dict(orm_object) == {"url": "http://example.com", "_id": db_object["_id"]}

  # Change the data a bit and save.
  # This would remove "name" from the doc.
  orm_object["url"] = "http://other.example.com"

  # Not authorized!
  with pytest.raises(Exception):
    orm_object.save()

  assert Sample.collection.find().count() == 2
  db_object = Sample.collection.find_one({"_id": db_object["_id"]})
  assert "name" in db_object

  orm_object.save(force=True)

  # Should not add anything new
  assert Sample.collection.find().count() == 2

  db_object = Sample.collection.find_one({"_id": db_object["_id"]})
  assert "name" not in db_object
  assert db_object["url"] == "http://other.example.com"

  orm_object = Sample.find_by_id(db_object["_id"], fields=["_id"])
  orm_object["name"] = "YYY"

  # This one should not overwrite unset fields.
  orm_object.save_partial()

  db_object = Sample.collection.find_one({"_id": db_object["_id"]})
  assert db_object["name"] == "YYY"
  assert db_object["url"] == "http://other.example.com"

  # Test the reload() method by changing the data from somewhere else
  Sample.collection.update({"_id": db_object["_id"]}, {"$set": {"name": "AAA"}})

  assert orm_object["name"] == "YYY"

  orm_object.reload()

  assert orm_object["name"] == "AAA"

  # Test .update() - local dict update()
  orm_object.update({"name": "BBB"})

  assert orm_object["name"] == "BBB"

  # Should not have changed the DB
  db_object = Sample.collection.find_one({"_id": db_object["_id"]})
  assert db_object["name"] == "AAA"


def test_save_partial_dotdict(Sample):

    now = datetime.datetime.now().replace(microsecond=0)
    store = Sample({
        "name": "XXX",
        "priceparsing": {
            "normal": {
                "consecutiveoks": 10, "lastdatemoderated": now
            }
        }
    })
    store.save()

    store.reload()
    assert store["priceparsing"]["normal"]["consecutiveoks"] == 10

    store.save_partial({"priceparsing.normal.consecutiveoks": 30})
    assert store["priceparsing"]["normal"]["consecutiveoks"] == 30
    assert store["priceparsing"]["normal"]["lastdatemoderated"] == now

    store.reload()
    assert store["priceparsing"]["normal"]["consecutiveoks"] == 30
    assert store["priceparsing"]["normal"]["lastdatemoderated"] == now