import logging
import os
import secrets

import aioredis
import boto3
import pytest
import redis as pyredis
from fastapi.testclient import TestClient
from sqlalchemy import text
from sqlalchemy_utils import create_database, database_exists, drop_database
from sqlmodel import Session, create_engine

from iris.agent.settings import AgentSettings
from iris.api.authentication import (
    current_active_user,
    current_superuser,
    current_verified_user,
)
from iris.api.main import app
from iris.api.settings import APISettings
from iris.commons.clickhouse import ClickHouse
from iris.commons.dependencies import get_settings
from iris.commons.models.base import Base
from iris.commons.redis import Redis
from iris.commons.settings import CommonSettings
from iris.commons.storage import Storage
from iris.commons.utils import json_serializer
from iris.worker import WorkerSettings

pytest.register_assert_rewrite("tests.assertions")
pytest_plugins = ["tests.fixtures.models", "tests.fixtures.storage"]


def should_cleanup():
    return os.environ.get("IRIS_TEST_CLEANUP", "") != "0"


@pytest.fixture
def logger():
    return logging.getLogger(__name__)


@pytest.fixture
def settings():
    namespace = secrets.token_hex(nbytes=4)
    print(f"@{namespace}", end=" ")
    # Redis has 16 databases by default, we use the last one for testing.
    return CommonSettings(
        CLICKHOUSE_PUBLIC_USER="public",
        CLICKHOUSE_DATABASE="iris_test",
        DATABASE_URL=f"postgresql://iris:iris@postgres.docker.localhost/iris-test-{namespace}",
        S3_PREFIX=f"iris-test-{namespace}",
        S3_PUBLIC_RESOURCES=["arn:aws:s3:::test-public-exports/*"],
        REDIS_NAMESPACE=f"iris-test-{namespace}",
        REDIS_URL="redis://default:iris@redis.docker.localhost?db=15",
        RETRY_TIMEOUT=-1,
    )


@pytest.fixture
def api_settings(settings):
    return APISettings(
        API_CORS_ALLOW_ORIGIN="https://example.org,http://localhost:8000",
        **settings.dict(),
    )


@pytest.fixture
def agent_settings(settings, tmp_path):
    return AgentSettings(
        **settings.dict(),
        AGENT_CARACAL_SNIFFER_WAIT_TIME=1,
        AGENT_MIN_TTL=0,
        AGENT_RESULTS_DIR_PATH=tmp_path / "agent_results",
        AGENT_TARGETS_DIR_PATH=tmp_path / "agent_targets",
    )


@pytest.fixture
def worker_settings(settings, tmp_path):
    return WorkerSettings(
        **settings.dict(),
        WORKER_RESULTS_DIR_PATH=tmp_path / "worker_results",
        WORKER_MAX_OPEN_FILES=128,
    )


@pytest.fixture
def clickhouse(settings, logger):
    return ClickHouse(settings, logger)


@pytest.fixture
def engine(settings):
    engine = create_engine(settings.DATABASE_URL, json_serializer=json_serializer)
    if not database_exists(engine.url):
        create_database(engine.url)
    Base.metadata.create_all(engine)
    return engine


@pytest.fixture
async def redis(settings, logger):
    client = aioredis.from_url(settings.REDIS_URL, decode_responses=True)
    yield Redis(client, settings, logger)
    await client.close()


@pytest.fixture
def session(engine):
    with Session(engine) as session:
        yield session


@pytest.fixture
def storage(settings, logger):
    return Storage(settings, logger)


@pytest.fixture
def make_client(engine, api_settings):
    def _make_client(user=None):
        if user and user.is_active:
            app.dependency_overrides[current_active_user] = lambda: user
        if user and user.is_active and user.is_verified:
            app.dependency_overrides[current_verified_user] = lambda: user
        if user and user.is_active and user.is_verified and user.is_superuser:
            app.dependency_overrides[current_superuser] = lambda: user
        app.dependency_overrides[get_settings] = lambda: api_settings
        return TestClient(app)

    yield _make_client
    app.dependency_overrides.clear()


@pytest.fixture(autouse=True, scope="session")
def cleanup_redis():
    yield
    if should_cleanup():
        redis_ = pyredis.from_url("redis://default:iris@redis.docker.localhost?db=15")
        redis_.flushdb()
        redis_.close()


@pytest.fixture(autouse=True, scope="session")
def cleanup_database():
    yield
    if should_cleanup():
        # TODO: Cleanup/simplify this code.
        engine = create_engine("postgresql://iris:iris@postgres.docker.localhost")
        with engine.connect() as conn:
            databases = conn.execute(
                text(
                    """
                        SELECT datname
                        FROM pg_database
                        WHERE datistemplate = false AND datname LIKE 'iris-test-%'
                    """
                )
            ).all()
        for (database,) in databases:
            drop_database(
                f"postgresql://iris:iris@postgres.docker.localhost/{database}"
            )


@pytest.fixture(autouse=True, scope="session")
def cleanup_s3():
    yield
    if should_cleanup():
        s3 = boto3.client(
            "s3",
            aws_access_key_id="minioadmin",
            aws_secret_access_key="minioadmin",
            endpoint_url="http://minio.docker.localhost",
        )
        buckets = s3.list_buckets()
        buckets = [x["Name"] for x in buckets["Buckets"]]
        for bucket in buckets:
            if "test-" in bucket:
                objects = s3.list_objects_v2(Bucket=bucket)
                if objects["KeyCount"]:
                    objects = [{"Key": x["Key"]} for x in objects.get("Contents", [])]
                    s3.delete_objects(Bucket=bucket, Delete=dict(Objects=objects))
                s3.delete_bucket(Bucket=bucket)
        # https://github.com/boto/botocore/pull/1810
        s3._endpoint.http_session._manager.clear()

logging.getLogger secrets.token_hex iris.commons.settings.CommonSettings sqlmodel.create_engine iris.commons.redis.Redis boto3.client iris.commons.storage.Storage pytest.fixture redis.from_url pytest.register_assert_rewrite aioredis.from_url iris.commons.models.base.Base.metadata.create_all sqlalchemy_utils.database_exists iris.commons.clickhouse.ClickHouse iris.api.main.app.dependency_overrides.clear sqlalchemy_utils.drop_database sqlalchemy.text fastapi.testclient.TestClient os.environ.get sqlmodel.Session sqlalchemy_utils.create_database
[((887, 937), 'pytest.register_assert_rewrite', 'pytest.register_assert_rewrite', (['"""tests.assertions"""'], {}), "('tests.assertions')\n", (917, 937), False, 'import pytest\n'), ((3884, 3929), 'pytest.fixture', 'pytest.fixture', ([], {'autouse': '(True)', 'scope': '"""session"""'}), "(autouse=True, scope='session')\n", (3898, 3929), False, 'import pytest\n'), ((4124, 4169), 'pytest.fixture', 'pytest.fixture', ([], {'autouse': '(True)', 'scope': '"""session"""'}), "(autouse=True, scope='session')\n", (4138, 4169), False, 'import pytest\n'), ((4865, 4910), 'pytest.fixture', 'pytest.fixture', ([], {'autouse': '(True)', 'scope': '"""session"""'}), "(autouse=True, scope='session')\n", (4879, 4910), False, 'import pytest\n'), ((1132, 1159), 'logging.getLogger', 'logging.getLogger', (['__name__'], {}), '(__name__)\n', (1149, 1159), False, 'import logging\n'), ((1210, 1237), 'secrets.token_hex', 'secrets.token_hex', ([], {'nbytes': '(4)'}), '(nbytes=4)\n', (1227, 1237), False, 'import secrets\n'), ((1359, 1775), 'iris.commons.settings.CommonSettings', 'CommonSettings', ([], {'CLICKHOUSE_PUBLIC_USER': '"""public"""', 'CLICKHOUSE_DATABASE': '"""iris_test"""', 'DATABASE_URL': 'f"""postgresql://iris:iris@postgres.docker.localhost/iris-test-{namespace}"""', 'S3_PREFIX': 'f"""iris-test-{namespace}"""', 'S3_PUBLIC_RESOURCES': "['arn:aws:s3:::test-public-exports/*']", 'REDIS_NAMESPACE': 'f"""iris-test-{namespace}"""', 'REDIS_URL': '"""redis://default:iris@redis.docker.localhost?db=15"""', 'RETRY_TIMEOUT': '(-1)'}), "(CLICKHOUSE_PUBLIC_USER='public', CLICKHOUSE_DATABASE=\n    'iris_test', DATABASE_URL=\n    f'postgresql://iris:iris@postgres.docker.localhost/iris-test-{namespace}',\n    S3_PREFIX=f'iris-test-{namespace}', S3_PUBLIC_RESOURCES=[\n    'arn:aws:s3:::test-public-exports/*'], REDIS_NAMESPACE=\n    f'iris-test-{namespace}', REDIS_URL=\n    'redis://default:iris@redis.docker.localhost?db=15', RETRY_TIMEOUT=-1)\n", (1373, 1775), False, 'from iris.commons.settings import CommonSettings\n'), ((2577, 2605), 'iris.commons.clickhouse.ClickHouse', 'ClickHouse', (['settings', 'logger'], {}), '(settings, logger)\n', (2587, 2605), False, 'from iris.commons.clickhouse import ClickHouse\n'), ((2659, 2728), 'sqlmodel.create_engine', 'create_engine', (['settings.DATABASE_URL'], {'json_serializer': 'json_serializer'}), '(settings.DATABASE_URL, json_serializer=json_serializer)\n', (2672, 2728), False, 'from sqlmodel import Session, create_engine\n'), ((2809, 2841), 'iris.commons.models.base.Base.metadata.create_all', 'Base.metadata.create_all', (['engine'], {}), '(engine)\n', (2833, 2841), False, 'from iris.commons.models.base import Base\n'), ((2926, 2986), 'aioredis.from_url', 'aioredis.from_url', (['settings.REDIS_URL'], {'decode_responses': '(True)'}), '(settings.REDIS_URL, decode_responses=True)\n', (2943, 2986), False, 'import aioredis\n'), ((3212, 3237), 'iris.commons.storage.Storage', 'Storage', (['settings', 'logger'], {}), '(settings, logger)\n', (3219, 3237), False, 'from iris.commons.storage import Storage\n'), ((3848, 3880), 'iris.api.main.app.dependency_overrides.clear', 'app.dependency_overrides.clear', ([], {}), '()\n', (3878, 3880), False, 'from iris.api.main import app\n'), ((1042, 1081), 'os.environ.get', 'os.environ.get', (['"""IRIS_TEST_CLEANUP"""', '""""""'], {}), "('IRIS_TEST_CLEANUP', '')\n", (1056, 1081), False, 'import os\n'), ((2740, 2767), 'sqlalchemy_utils.database_exists', 'database_exists', (['engine.url'], {}), '(engine.url)\n', (2755, 2767), False, 'from sqlalchemy_utils import create_database, database_exists, drop_database\n'), ((2777, 2804), 'sqlalchemy_utils.create_database', 'create_database', (['engine.url'], {}), '(engine.url)\n', (2792, 2804), False, 'from sqlalchemy_utils import create_database, database_exists, drop_database\n'), ((2997, 3028), 'iris.commons.redis.Redis', 'Redis', (['client', 'settings', 'logger'], {}), '(client, settings, logger)\n', (3002, 3028), False, 'from iris.commons.redis import Redis\n'), ((3102, 3117), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (3109, 3117), False, 'from sqlmodel import Session, create_engine\n'), ((3804, 3819), 'fastapi.testclient.TestClient', 'TestClient', (['app'], {}), '(app)\n', (3814, 3819), False, 'from fastapi.testclient import TestClient\n'), ((4003, 4072), 'redis.from_url', 'pyredis.from_url', (['"""redis://default:iris@redis.docker.localhost?db=15"""'], {}), "('redis://default:iris@redis.docker.localhost?db=15')\n", (4019, 4072), True, 'import redis as pyredis\n'), ((4290, 4355), 'sqlmodel.create_engine', 'create_engine', (['"""postgresql://iris:iris@postgres.docker.localhost"""'], {}), "('postgresql://iris:iris@postgres.docker.localhost')\n", (4303, 4355), False, 'from sqlmodel import Session, create_engine\n'), ((4977, 5114), 'boto3.client', 'boto3.client', (['"""s3"""'], {'aws_access_key_id': '"""minioadmin"""', 'aws_secret_access_key': '"""minioadmin"""', 'endpoint_url': '"""http://minio.docker.localhost"""'}), "('s3', aws_access_key_id='minioadmin', aws_secret_access_key=\n    'minioadmin', endpoint_url='http://minio.docker.localhost')\n", (4989, 5114), False, 'import boto3\n'), ((4754, 4831), 'sqlalchemy_utils.drop_database', 'drop_database', (['f"""postgresql://iris:iris@postgres.docker.localhost/{database}"""'], {}), "(f'postgresql://iris:iris@postgres.docker.localhost/{database}')\n", (4767, 4831), False, 'from sqlalchemy_utils import create_database, database_exists, drop_database\n'), ((4449, 4655), 'sqlalchemy.text', 'text', (['"""\n                        SELECT datname\n                        FROM pg_database\n                        WHERE datistemplate = false AND datname LIKE \'iris-test-%\'\n                    """'], {}), '(\n    """\n                        SELECT datname\n                        FROM pg_database\n                        WHERE datistemplate = false AND datname LIKE \'iris-test-%\'\n                    """\n    )\n', (4453, 4655), False, 'from sqlalchemy import text\n')]