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 sqlalchemy import exc
from database import Base
from dev import app, get_db
from models import Movie

import pytest
import crud
import contextlib

import random
import inspect

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)


client = TestClient(app)


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


app.dependency_overrides[get_db] = override_get_db


def clear_db():
    # Make this a more generic functional tool for test
    meta = MetaData()
    with contextlib.closing(engine.connect()) as con:
        trans = con.begin()
        for table in reversed(meta.sorted_tables):
            con.execute(table.delete())
        trans.commit()


@contextlib.contextmanager
def db_context():
    yield from override_get_db()


def rand_name():
    import sys

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


def test_create_moviem_models():
    name = rand_name()
    movie = Movie(title=name)
    assert movie.title == name


def test_sample_crud():
    name = rand_name()

    with db_context() as db:
        movie = crud.create_movie(db, title=name, genres=["Yes", "No"])
    assert movie.title == name


def test_genre_custom_type_serialize_value():
    name = rand_name()

    genres = ["Yes", "No"]
    with db_context() as db:
        movie = crud.create_movie(db, title=name, genres=genres)
    assert movie.genres == genres


def test_genre_custom_type_serialize_type():
    name = rand_name()
    genres = ["Yes", "No"]
    csv_genres = ",".join(genres)

    try:
        with db_context() as db:
            movie = crud.create_movie(db, title=name, genres=csv_genres)
    except (ValueError, exc.StatementError) as error:
        assert "tuple" in str(error)
    else:
        raise RuntimeError("Exception should have been raised")


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

    N = 10
    names = []
    with db_context() as db:
        for _ in range(N):
            name = rand_name()

            names.append(name)
            crud.create_movie(db, title=name, genres=["Animated", "Paropaganda"])

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