# -*- coding: utf-8 -*-

from flexmock import flexmock, flexmock_teardown
from .. import OratorTestCase, mock
from ..utils import MockModel, MockQueryBuilder, MockConnection, MockProcessor
from orator.query.grammars.grammar import QueryGrammar
from orator.query.builder import QueryBuilder
from orator.orm.builder import Builder
from orator.orm.model import Model
from orator.orm import belongs_to, has_many, scope
from orator.exceptions.orm import ModelNotFound
from orator.orm.collection import Collection
from orator.connections import Connection
from orator.query.processors import QueryProcessor


class BuilderTestCase(OratorTestCase):
    def tearDown(self):
        flexmock_teardown()

    def test_find_method(self):
        builder = Builder(self.get_mock_query_builder())
        builder.set_model(self.get_mock_model())
        builder.get_query().where = mock.MagicMock()
        builder.first = mock.MagicMock(return_value="baz")

        result = builder.find("bar", ["column"])

        builder.get_query().where.assert_called_once_with("foo_table.foo", "=", "bar")
        self.assertEqual("baz", result)

    def test_find_or_new_model_found(self):
        model = self.get_mock_model()
        model.find_or_new = mock.MagicMock(return_value="baz")

        builder = Builder(self.get_mock_query_builder())
        builder.set_model(model)
        builder.get_query().where = mock.MagicMock()
        builder.first = mock.MagicMock(return_value="baz")

        expected = model.find_or_new("bar", ["column"])
        result = builder.find("bar", ["column"])

        builder.get_query().where.assert_called_once_with("foo_table.foo", "=", "bar")
        self.assertEqual(expected, result)

    def test_find_or_new_model_not_found(self):
        model = self.get_mock_model()
        model.find_or_new = mock.MagicMock(return_value=self.get_mock_model())

        builder = Builder(self.get_mock_query_builder())
        builder.set_model(model)
        builder.get_query().where = mock.MagicMock()
        builder.first = mock.MagicMock(return_value=None)

        result = model.find_or_new("bar", ["column"])
        find_result = builder.find("bar", ["column"])

        builder.get_query().where.assert_called_once_with("foo_table.foo", "=", "bar")
        self.assertIsNone(find_result)
        self.assertIsInstance(result, Model)

    def test_find_or_fail_raises_model_not_found_exception(self):
        model = self.get_mock_model()

        builder = Builder(self.get_mock_query_builder())
        builder.set_model(model)
        builder.get_query().where = mock.MagicMock()
        builder.first = mock.MagicMock(return_value=None)

        self.assertRaises(ModelNotFound, builder.find_or_fail, "bar", ["column"])

        builder.get_query().where.assert_called_once_with("foo_table.foo", "=", "bar")

        builder.first.assert_called_once_with(["column"])

    def test_find_or_fail_with_many_raises_model_not_found_exception(self):
        model = self.get_mock_model()

        builder = Builder(self.get_mock_query_builder())
        builder.set_model(model)
        builder.get_query().where_in = mock.MagicMock()
        builder.get = mock.MagicMock(return_value=Collection([1]))

        self.assertRaises(ModelNotFound, builder.find_or_fail, [1, 2], ["column"])

        builder.get_query().where_in.assert_called_once_with("foo_table.foo", [1, 2])

        builder.get.assert_called_once_with(["column"])

    def test_first_or_fail_raises_model_not_found_exception(self):
        model = self.get_mock_model()

        builder = Builder(self.get_mock_query_builder())
        builder.set_model(model)
        builder.first = mock.MagicMock(return_value=None)

        self.assertRaises(ModelNotFound, builder.first_or_fail, ["column"])

        builder.first.assert_called_once_with(["column"])

    def test_find_with_many(self):
        model = self.get_mock_model()

        builder = Builder(self.get_mock_query_builder())
        builder.set_model(model)
        builder.get_query().where_in = mock.MagicMock()
        builder.get = mock.MagicMock(return_value="baz")

        result = builder.find([1, 2], ["column"])
        self.assertEqual("baz", result)

        builder.get_query().where_in.assert_called_once_with("foo_table.foo", [1, 2])

        builder.get.assert_called_once_with(["column"])

    def test_first(self):
        model = self.get_mock_model()

        builder = Builder(self.get_mock_query_builder())
        builder.set_model(model)
        builder.take = mock.MagicMock(return_value=builder)
        builder.get = mock.MagicMock(return_value=Collection(["bar"]))

        result = builder.first()
        self.assertEqual("bar", result)

        builder.take.assert_called_once_with(1)

        builder.get.assert_called_once_with(["*"])

    def test_get_loads_models_and_hydrates_eager_relations(self):
        flexmock(Builder)
        builder = Builder(self.get_mock_query_builder())
        builder.should_receive("get_models").with_args(["foo"]).and_return(["bar"])
        builder.should_receive("eager_load_relations").with_args(["bar"]).and_return(
            ["bar", "baz"]
        )
        builder.set_model(self.get_mock_model())
        builder.get_model().new_collection = mock.MagicMock(
            return_value=Collection(["bar", "baz"])
        )

        results = builder.get(["foo"])
        self.assertEqual(["bar", "baz"], results.all())

        builder.get_model().new_collection.assert_called_with(["bar", "baz"])

    def test_get_does_not_eager_relations_when_no_results_are_returned(self):
        flexmock(Builder)
        builder = Builder(self.get_mock_query_builder())
        builder.should_receive("get_models").with_args(["foo"]).and_return(["bar"])
        builder.should_receive("eager_load_relations").with_args(["bar"]).and_return([])
        builder.set_model(self.get_mock_model())
        builder.get_model().new_collection = mock.MagicMock(return_value=Collection([]))

        results = builder.get(["foo"])
        self.assertEqual([], results.all())

        builder.get_model().new_collection.assert_called_with([])

    def test_pluck_with_model_found(self):
        builder = Builder(self.get_mock_query_builder())

        model = {"name": "foo"}
        builder.first = mock.MagicMock(return_value=model)

        self.assertEqual("foo", builder.pluck("name"))

        builder.first.assert_called_once_with(["name"])

    def test_pluck_with_model_not_found(self):
        builder = Builder(self.get_mock_query_builder())

        builder.first = mock.MagicMock(return_value=None)

        self.assertIsNone(builder.pluck("name"))

    def test_chunk(self):
        query_builder = self.get_mock_query_builder()
        query_results = [["foo1", "foo2"], ["foo3"]]
        query_builder.chunk = mock.MagicMock(return_value=query_results)

        builder = Builder(query_builder)
        model = self.get_mock_model()
        builder.set_model(model)

        results = [Collection(["foo1", "foo2"]), Collection(["foo3"])]

        model.hydrate = mock.MagicMock(return_value=[])
        model.new_collection = mock.MagicMock(side_effect=results)
        model.get_connection_name = mock.MagicMock(return_value="foo")

        i = 0
        for result in builder.chunk(2):
            self.assertEqual(result, results[i])

            i += 1

        self.assertEqual(i, 2)

        query_builder.chunk.assert_has_calls([mock.call(2)])
        model.hydrate.assert_has_calls(
            [mock.call(["foo1", "foo2"], "foo"), mock.call(["foo3"], "foo")]
        )
        model.new_collection.assert_has_calls([mock.call([]), mock.call([])])

    # TODO: lists with get mutators

    def test_lists_without_model_getters(self):
        builder = self.get_builder()
        builder.get_query().get = mock.MagicMock(
            return_value=[{"name": "bar"}, {"name": "baz"}]
        )
        builder.set_model(self.get_mock_model())
        builder.get_model().has_get_mutator = mock.MagicMock(return_value=False)

        result = builder.lists("name")
        self.assertEqual(["bar", "baz"], result)

        builder.get_query().get.assert_called_once_with(["name"])

    def test_get_models_hydrates_models(self):
        builder = Builder(self.get_mock_query_builder())
        records = Collection([{"name": "john", "age": 26}, {"name": "jane", "age": 28}])

        builder.get_query().get = mock.MagicMock(return_value=records)
        model = self.get_mock_model()
        builder.set_model(model)
        model.get_connection_name = mock.MagicMock(return_value="foo_connection")
        model.hydrate = mock.MagicMock(return_value=Collection(["hydrated"]))