import os.path
import unittest.mock

import pytest
from ellar.common.exceptions import ImproperConfiguration

from ellar_sql import EllarSQLService, model


def test_engine_per_bind(tmp_path, ignore_base):
    db_service = EllarSQLService(
        databases={"default": "sqlite://", "a": "sqlite://"},
        root_path=str(tmp_path),
    )
    assert db_service.engines["a"] is not db_service.engine


def test_config_engine_options(ignore_base):
    db_service = EllarSQLService(
        databases={"default": "sqlite://", "a": "sqlite://"},
        common_engine_options={"echo": True},
    )
    assert db_service.engine.echo
    assert db_service.engines["a"].echo


def test_init_engine_options(ignore_base):
    db_service = EllarSQLService(
        databases={"default": "sqlite://", "a": {"url": "sqlite://", "echo": True}},
        common_engine_options={"echo": False},
    )
    assert not db_service.engine.echo
    assert db_service.engines["a"].echo


def test_config_echo(ignore_base):
    db_service = EllarSQLService(databases={"default": "sqlite://"}, echo=True)
    assert db_service.engine.echo
    assert db_service.engine.pool.echo


@pytest.mark.parametrize(
    "value",
    [
        "sqlite://",
        model.engine.URL.create("sqlite"),
        {"url": "sqlite://"},
        {"url": model.engine.URL.create("sqlite")},
    ],
)
def test_url_type(value, ignore_base):
    db_service = EllarSQLService(
        databases={"default": "sqlite://", "a": value}, echo=True
    )
    assert str(db_service.engines["a"].url) == "sqlite://"


def test_no_default_database_error(ignore_base):
    with pytest.raises(ImproperConfiguration) as info:
        EllarSQLService(
            databases={},
        )
    e = "`default` database must be present in databases parameter: {}"
    assert str(info.value) == e


def test_sqlite_relative_path(ignore_base, tmp_path):
    db_service = EllarSQLService(
        databases={"default": "sqlite:///test.db"}, root_path=str(tmp_path)
    )
    db_service.create_all()
    assert not isinstance(db_service.engine.pool, model.pool.StaticPool)
    db_path = db_service.engine.url.database
    assert db_path.startswith(str(tmp_path))
    assert os.path.exists(db_path)


def test_sqlite_driver_level_uri(tmp_path, ignore_base):
    db_service = EllarSQLService(
        databases={"default": "sqlite:///file:test.db?uri=true"},
        root_path=str(tmp_path),
    )
    db_service.create_all()
    db_path = db_service.engine.url.database
    assert db_path is not None
    assert db_path.startswith(f"file:{tmp_path}")
    assert os.path.exists(db_path[5:])


@unittest.mock.patch.object(EllarSQLService, "_make_engine", autospec=True)
def test_sqlite_memory_defaults(
    make_engine: unittest.mock.Mock, tmp_path, ignore_base
) -> None:
    EllarSQLService(databases={"default": "sqlite://"}, root_path=str(tmp_path))
    options = make_engine.call_args[0][1]
    assert options["poolclass"] is model.pool.StaticPool
    assert options["connect_args"]["check_same_thread"] is False


@unittest.mock.patch.object(EllarSQLService, "_make_engine", autospec=True)
def test_mysql_defaults(make_engine: unittest.mock.Mock, tmp_path, ignore_base) -> None:
    EllarSQLService(databases={"default": "mysql:///test"}, root_path=str(tmp_path))
    options = make_engine.call_args[0][1]
    assert options["pool_recycle"] == 7200
    assert options["url"].query["charset"] == "utf8mb4"
