import pytest
from marshmallow import Schema, fields
from sqlalchemy import Column, Integer, String

from flask_resty import Api, GenericModelView
from flask_resty.testing import assert_response

# -----------------------------------------------------------------------------


@pytest.fixture
def models(app, db):
    class Widget(db.Model):
        __tablename__ = "widgets"

        id = Column(Integer, primary_key=True)
        name = Column(String, nullable=False)
        description = Column(String)

    with app.app_context():
        db.create_all()

    yield {"widget": Widget}

    with app.app_context():
        db.drop_all()


@pytest.fixture
def schemas():
    class WidgetSchema(Schema):
        id = fields.Integer(as_string=True)
        name = fields.String(required=True)
        description = fields.String()

    return {"widget": WidgetSchema()}


@pytest.fixture(autouse=True)
def routes(app, models, schemas):
    class WidgetViewBase(GenericModelView):
        model = models["widget"]
        schema = schemas["widget"]

    class WidgetListView(WidgetViewBase):
        def get(self):
            return self.list()

        def post(self):
            return self.create()

    class WidgetView(WidgetViewBase):
        def get(self, id):
            return self.retrieve(id)

        def put(self, id):
            return self.upsert(id)

        def patch(self, id):
            return self.update(id, partial=True)

        def delete(self, id):
            return self.destroy(id)

    api = Api(app)
    api.add_resource(
        "/widgets", WidgetListView, WidgetView, id_rule="<int:id>"
    )


@pytest.fixture(autouse=True)
def data(app, db, models):
    with app.app_context():
        db.session.add_all(
            (
                models["widget"](name="Foo", description="foo widget"),
                models["widget"](name="Bar", description="bar widget"),
                models["widget"](name="Baz", description="baz widget"),
            )
        )
        db.session.commit()


# -----------------------------------------------------------------------------


def test_list(client):
    response = client.get("/widgets")
    assert_response(
        response,
        200,
        [
            {"id": "1", "name": "Foo", "description": "foo widget"},
            {"id": "2", "name": "Bar", "description": "bar widget"},
            {"id": "3", "name": "Baz", "description": "baz widget"},
        ],
    )


def test_retrieve(client):
    response = client.get("/widgets/1")

    assert_response(
        response, 200, {"id": "1", "name": "Foo", "description": "foo widget"}
    )


def test_create(client):
    response = client.post(
        "/widgets", data={"name": "Qux", "description": "qux widget"}
    )
    assert response.headers["Location"] == "/widgets/4"

    assert_response(
        response, 201, {"id": "4", "name": "Qux", "description": "qux widget"}
    )


def test_update(client):
    response = client.patch(
        "/widgets/1", data={"id": "1", "description": "updated description"}
    )
    assert_response(
        response,
        200,
        {"id": "1", "name": "Foo", "description": "updated description"},
    )


def test_upsert_update(client):
    response = client.put(
        "/widgets/1",
        data={"id": "1", "name": "Foo", "description": "updated description"},
    )
    assert_response(
        response,
        200,
        {"id": "1", "name": "Foo", "description": "updated description"},
    )


def test_upsert_create(client):
    response = client.put(
        "/widgets/4",
        data={"id": "4", "name": "Qux", "description": "qux widget"},
    )
    assert response.headers["Location"] == "/widgets/4"

    assert_response(
        response, 201, {"id": "4", "name": "Qux", "description": "qux widget"}
    )


def test_destroy(client):
    destroy_response = client.delete("/widgets/1")
    assert_response(destroy_response, 204)

    retrieve_response = client.get("/widgets/1")
    assert_response(retrieve_response, 404)
