"""
This module contains fixtures for pytest -- objects that are set up by the test framework and summonable by name.
They can be created and torn down at different scopes: the entire test session, within a module, or around every
test function.
For more info, see here: https://docs.pytest.org/en/latest/fixture.html
"""

from typing import Iterator

import flask.testing
import pytest
import sqlalchemy.engine
import sqlalchemy.orm
from typing import IO
from unittest import mock

from app import create_app
from app.auth import service_auth, userinfo
from app.db import db, model
from app.external.rawls import RawlsWorkspaceResponse


def _test_client() -> flask.testing.FlaskClient:
    """Builds a Flask client wired up for unit tests. Created once per test invocation and reused thereafter."""
    app = create_app()
    app.debug = True

    return app.test_client()


@pytest.fixture(scope="session")
def client() -> flask.testing.FlaskClient:
    """A FlaskClient fixture that's created once per test run. You should use this in most instances."""
    return _test_client()


@pytest.fixture(scope="function")
def client_with_modifiable_routes() -> flask.testing.FlaskClient:
    """A FlaskClient fixture that's recreated for every test function. You'll need this if you want to dynamically add
    URL handlers in your test, otherwise previously running tests will have flipped a switch in the app that asserts
    because you've already handled a request beforehand."""
    return _test_client()


@pytest.fixture(scope="session")
def _db_internal() -> Iterator[sqlalchemy.engine.Engine]:
    """Internal fixture for creating one sqlite db for test."""
    sqlite_db = sqlalchemy.create_engine('sqlite://')

    # Monkeypatch over the autogenerated database with the sqlite one.
    # It will get its tables recreated and dropped around every test function (see fresh_db below).
    db._db = sqlite_db

    yield sqlite_db


@pytest.fixture(scope="function", autouse=True)
def fresh_db(_db_internal: sqlalchemy.engine.Engine) -> Iterator[None]:
    """Empty the database of all data between tests."""
    model.Base.metadata.create_all(_db_internal)
    yield
    model.Base.metadata.drop_all(_db_internal)


@pytest.fixture(scope="function")
def fake_import() -> Iterator[model.Import]:
    yield model.Import("aa", "aa", "uuid", "project", "aa@aa.aa", "gs://aa/aa", "pfb")

@pytest.fixture(scope="function")
def fake_import_tdr_manifest_gcp() -> Iterator[model.Import]:
    yield model.Import("bb", "bb", "uuid2", "project2", "bb@bb.bb", "gs://bb/bb", "tdrexport")

@pytest.fixture(scope="function")
def fake_import_tdr_manifest_azure() -> Iterator[model.Import]:
    yield model.Import("bb", "bb", "uuid2", "project2", "bb@bb.bb", "https://snp.blob.core.windows.net/metadata/manifests/manifest.json", "tdrexport")

@pytest.fixture(scope="function")
def incoming_valid_pubsub(monkeypatch) -> Iterator[None]:
    monkeypatch.setattr(service_auth, "verify_pubsub_jwt", mock.MagicMock())
    yield


@pytest.fixture(scope="function")
def pubsub_fake_env(monkeypatch) -> Iterator[None]:
    monkeypatch.setenv("PUBSUB_PROJECT", "pubsub-project")
    monkeypatch.setenv("PUBSUB_TOPIC", "pubsub-topic")
    monkeypatch.setenv("PUBSUB_TOPIC", "pubsub-subscription")
    monkeypatch.setenv("PUBSUB_TOKEN", "token")
    monkeypatch.setenv("PUBSUB_AUDIENCE", "aud")
    monkeypatch.setenv("PUBSUB_ACCOUNT", "sa@sa.org")
    yield


@pytest.fixture(scope="function")
def fake_pfb() -> Iterator[IO]:
    with open("app/tests/empty.avro", 'rb') as out:
        yield out

@pytest.fixture(scope="function")
def fake_tdr_manifest() -> Iterator[IO]:
    with open("app/tests/response_1638551384572.json", 'rb') as out:
        yield out

@pytest.fixture(scope="function")
def fake_parquet() -> Iterator[IO]:
    with open("app/tests/empty.parquet", 'rb') as out:
        yield out

@pytest.fixture(scope="function")
def sam_valid_user(monkeypatch):
    """Makes us think that the current user is valid in Sam."""
    monkeypatch.setattr("app.external.sam.validate_user",
                        mock.MagicMock(return_value=userinfo.UserInfo("123456", "hello@bees.com", True)))

@pytest.fixture(scope="function")
def sam_valid_pet_key(monkeypatch):
    monkeypatch.setattr("app.external.sam.admin_get_pet_key",
                        mock.MagicMock(return_value={}))


@pytest.fixture(scope="function")
def user_has_ws_access(monkeypatch):
    """Makes us think that the user has access to the workspace in Rawls."""
    monkeypatch.setattr("app.auth.user_auth.workspace_uuid_and_project_with_auth",
                        mock.MagicMock(return_value=RawlsWorkspaceResponse("some-uuid", "some-project", set(), "fc-secure-12345678-a901-23b4-c5d6-7ef8a90b1cd2")))


@pytest.fixture(scope="function")
def pubsub_publish(monkeypatch):
    """Replace the publish to google pub/sub with a no-op one"""
    monkeypatch.setattr("app.external.pubsub.publish_self", mock.MagicMock())
