from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from sqlalchemy import MetaData

from database import Base
from dev import app, get_db
from models import Movie

import pytest
import crud
import contextlib

import random
import inspect
import unittest

SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"

engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    connect_args={"check_same_thread": False},
    poolclass=StaticPool,
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


Base.metadata.create_all(bind=engine)


def override_get_db():
    try:
        db = TestingSessionLocal()
        yield db
    finally:
        db.close()


app.dependency_overrides[get_db] = override_get_db

client = TestClient(app)


def rand_name():
    import sys

    caller = sys._getframe(1).f_code.co_name
    name = f"{caller}_{random.randint(1, 1000)}"
    return name


class BaseCrud(unittest.TestCase):
    def setUp(self):
        name = f"rand_{random.randint(1, 1000)}"
        self.create_payload = {
            "title": name,
        }

    def test_get_delete_movie_404_if_not_found(self):
        response = client.get("/movies/-1")
        assert response.status_code == 404
        response_delete = client.delete("/movies/-1")
        assert response_delete.status_code == 404

    def test_create_movie_api(self):
        response = client.post("/movies/", json=self.create_payload)

        assert response.status_code == 200
        movie_id = response.json()["id"]
        assert f"Created {self.create_payload['title']}" in response.json()["message"]
        response = client.get(f"/movies/{movie_id}")
        assert response.json()["title"] == self.create_payload["title"]

    def test_delete_movie(self):
        response = client.post("/movies/", json=self.create_payload)
        movie_id = response.json()["id"]
        created_movie_path = f"/movies/{movie_id}"

        response_delete = client.delete(created_movie_path)

        response_missing = client.get(created_movie_path)

        assert response_delete.status_code == 204
        assert response_missing.status_code == 404

    def test_update_movie_api(self):
        self.create_payload["genres"] = ["anime"]

        self.create_payload["description"] = legacy_description = "will be deleted"
        response = client.post("/movies/", json=self.create_payload)
        movie_id = response.json()["id"]
        created_movie_path = f"/movies/{movie_id}"

        new_name = self.create_payload["title"].replace("rand", "update")
        new_genres = ["Drama", "war"]
        response_get = client.get(f"/movies/{movie_id}")

        assert response_get.json()["title"] != new_name
        assert response_get.json()["genres"] != new_genres

        response_update = client.put(
            created_movie_path, json={"title": new_name, "genres": new_genres}
        )
        assert response_update.status_code == 200

        response_get = client.get(f"/movies/{movie_id}")
        assert response_get.json()["title"] == new_name
        assert response_get.json()["genres"] == new_genres
        assert response_get.json()["description"] == ""

    def test_list_movies(self):
        response = client.get("/movies/")
        assert response.status_code == 200
        # assert response.json() == []

        N = 10
        names = []
        for _ in range(N):
            name = rand_name()

            names.append(name)
            self.create_payload["title"] = name
            response = client.post("/movies/", json=self.create_payload)
            assert response.status_code == 200

        movies = client.get("/movies/")
        movies_by_title = {m["title"]: m for m in movies.json()}
        found = list(movies_by_title[title] for title in names)
        assert all(movies_by_title[title] for title in names)


class ApiTestCase(unittest.TestCase):
    def test_payload_content_in_and_out_loopback(self):
        be_the_fun_in_de_funes = {
            "id": 1,
            "title": "La Grande Vadrouille",
            "description": "During World War II, two French civilians and a downed English Bomber Crew set "
            "out from Paris to cross the demarcation line between Nazi-occupied Northern France and the "
            "South. From there they will be able to escape to England. First, they must avoid German troops -"
            "and the consequences of their own blunders.",
            "genres": ["Comedy", "War"],
            "release_date": "1966-12-07",
            "vote_average": 7.7,
            "vote_count": 1123,
        }

        domain_keys = sorted(
            {k for k in be_the_fun_in_de_funes if k not in ["id"]}
        )  # Make it deterministic
        non_primtive = ["genres", "release_date"]

        payload = {k: be_the_fun_in_de_funes[k] for k in domain_keys}
        # FIXME
        response = client.post("/movies/", json=payload)

        assert response.status_code == 200
        movie_id = response.json()["id"]

        loopback_fetch = client.get(f"/movies/{movie_id}")
        assert loopback_fetch.status_code == 200
        loopback_payload = loopback_fetch.json()
        # check for keys
        for attribute_name in domain_keys:
            with self.subTest(attribute_name=attribute_name):
                assert attribute_name in loopback_payload
                if attribute_name not in non_primtive:
                    assert (
                        loopback_payload[attribute_name]
                        == be_the_fun_in_de_funes[attribute_name]
                    )

    def test_payload_content_bad_format_status_code(self):
        be_the_fun_in_de_funes = {
            "id": 1,
            "title": "La Grande Vadrouille",
            "description": "During World War II, two French civilians and a downed English Bomber Crew set "
            "out from Paris to cross the demarcation line between Nazi-occupied Northern France and the "
            "South. From there they will be able to escape to England. First, they must avoid German troops -"
            "and the consequences of their own blunders.",
            "genres": ["Comedy", "War"],
            "release_date": "1966-12-07",
            "vote_average": 7.7,
            "vote_count": 1123,
        }

        domain_keys = sorted(
            {k for k in be_the_fun_in_de_funes if k not in ["id"]}
        )  # Make it deterministic

        payload = {k: be_the_fun_in_de_funes[k] for k in domain_keys}

        missing_key = "title"

        payload.pop(missing_key)

        response = client.post("/movies/", json=payload)

        assert 400 <= response.status_code < 500

        assert missing_key in response.text

    def test_payload_content_bad_format_detail(self):
        be_the_fun_in_de_funes = {
            "id": 1,
            "title": "La Grande Vadrouille",
            "description": "During World War II, two French civilians and a downed English Bomber Crew set "
            "out from Paris to cross the demarcation line between Nazi-occupied Northern France and the "
            "South. From there they will be able to escape to England. First, they must avoid German troops -"
            "and the consequences of their own blunders.",
            "genres": ["Comedy", "War"],
            "release_date": "1966-12-07",
            "vote_average": 7.7,
            "vote_count": 1123,
        }

        domain_keys = sorted(
            {k for k in be_the_fun_in_de_funes if k not in ["id"]}
        )  # Make it deterministic

        payload = {k: be_the_fun_in_de_funes[k] for k in domain_keys}

        missing_key = "title"

        payload.pop(missing_key)

        response = client.post("/movies/", json=payload)

        assert 400 <= response.status_code < 500

        assert response.status_code == 400

    def test_payload_content_in_and_out_loopback(self):
        be_the_fun_in_de_funes = {
            "id": 1,
            "title": "La Grande Vadrouille",
            "description": "During World War II, two French civilians and a downed English Bomber Crew set "
            "out from Paris to cross the demarcation line between Nazi-occupied Northern France and the "
            "South. From there they will be able to escape to England. First, they must avoid German troops -"
            "and the consequences of their own blunders.",
            "genres": ["Comedy", "War"],
            "release_date": "1966-12-07",
            "vote_average": 7.7,
            "vote_count": 1123,
        }

        domain_keys = sorted(
            {k for k in be_the_fun_in_de_funes if k not in ["id"]}
        )  # Make it deterministic
        non_primtive = ["genres", "release_date"]

        payload = {k: be_the_fun_in_de_funes[k] for k in domain_keys}
        # FIXME
        response = client.post("/movies/", json=payload)

        assert response.status_code == 200
        movie_id = response.json()["id"]

        loopback_fetch = client.get(f"/movies/{movie_id}")
        assert loopback_fetch.status_code == 200
        loopback_payload = loopback_fetch.json()
        # check for keys
        for attribute_name in domain_keys:
            with self.subTest(attribute_name=attribute_name):
                assert attribute_name in loopback_payload
                if attribute_name not in non_primtive:
                    assert (
                        loopback_payload[attribute_name]
                        == be_the_fun_in_de_funes[attribute_name]
                    )

    def test_payload_content_in_and_out_loopback_non_primitive(self):
        be_the_fun_in_de_funes = {
            "id": 1,
            "title": "La Grande Vadrouille",
            "description": "During World War II, two French civilians and a downed English Bomber Crew set "
            "out from Paris to cross the demarcation line between Nazi-occupied Northern France and the "
            "South. From there they will be able to escape to England. First, they must avoid German troops -"
            "and the consequences of their own blunders.",
            "genres": ["Comedy", "War"],
            "release_date": "1966-12-07",
            "vote_average": 7.7,
            "vote_count": 1123,
        }

        domain_keys = sorted(
            {k for k in be_the_fun_in_de_funes if k not in ["id"]}
        )  # Make it deterministic

        payload = {k: be_the_fun_in_de_funes[k] for k in domain_keys}
        response = client.post("/movies/", json=payload)

        assert response.status_code == 200
        movie_id = response.json()["id"]

        loopback_fetch = client.get(f"/movies/{movie_id}")
        assert loopback_fetch.status_code == 200
        loopback_payload = loopback_fetch.json()
        # check for keys
        for attribute_name in domain_keys:
            assert (
                loopback_payload[attribute_name]
                == be_the_fun_in_de_funes[attribute_name]
            )
