from pathlib import Path

import pytest
from sqlalchemy.pool import NullPool

from app.core.data_source.errors import (
    DataSourceConfigurationInvalid,
    DataSourceTypeUnsupported,
)
from app.core.data_source.models import (
    DataSourceCredential,
    DataSourceDefinition,
)


def make_definition(database_type, **overrides):
    values = {
        "uid": "01900000-0000-7000-8000-000000000010",
        "name_en": "warehouse",
        "database_type": database_type,
        "host": "db.internal",
        "port": 5432 if database_type == "postgresql" else 3306,
        "database": "analytics",
        "schema": "public" if database_type == "postgresql" else None,
    }
    values.update(overrides)
    return DataSourceDefinition(**values)


@pytest.mark.parametrize(
    ("database_type", "drivername"),
    [
        ("postgresql", "postgresql+psycopg2"),
        ("mysql", "mysql+pymysql"),
    ],
)
def test_adapter_uses_allowlisted_driver(database_type, drivername):
    from app.core.data_source.adapters import adapter_for

    adapter = adapter_for(database_type)
    url = adapter.build_url(
        make_definition(database_type),
        DataSourceCredential("reader", "secret"),
    )

    assert url.drivername == drivername
    assert url.password == "secret"


def test_unknown_driver_is_rejected():
    from app.core.data_source.adapters import adapter_for

    with pytest.raises(DataSourceTypeUnsupported):
        adapter_for("sqlite")


def test_adapter_rejects_arbitrary_connection_options(tmp_path):
    from app.core.data_source.adapters import adapter_for

    adapter = adapter_for("postgresql", certificate_dir=tmp_path)

    with pytest.raises(DataSourceConfigurationInvalid):
        adapter.validate_options({"driver": "custom"})
    with pytest.raises(DataSourceConfigurationInvalid):
        adapter.validate_options({"sslrootcert": "/tmp/outside.pem"})

    certificate = tmp_path / "root.pem"
    certificate.write_text("test certificate", encoding="utf-8")
    assert adapter.validate_options(
        {"sslmode": "verify-full", "sslrootcert": str(certificate)}
    ) == {
        "sslmode": "verify-full",
        "sslrootcert": str(certificate.resolve()),
    }


class FakeConnection:
    def __init__(self):
        self.executed = []

    def execute(self, statement):
        self.executed.append(str(statement))

    def __enter__(self):
        return self

    def __exit__(self, *_args):
        return False


class FakeEngine:
    def __init__(self):
        self.connection = FakeConnection()
        self.disposed = False

    def connect(self):
        return self.connection

    def dispose(self):
        self.disposed = True


def test_read_purpose_configures_database_read_only_transaction():
    from app.core.data_source.adapters import adapter_for

    connection = FakeConnection()
    adapter_for("postgresql").configure_transaction(
        connection,
        purpose="metadata_preview",
    )

    assert connection.executed == ["SET TRANSACTION READ ONLY"]


def test_connection_test_uses_null_pool_and_disposes(monkeypatch):
    from app.core.data_source.adapters import adapter_for
    from app.core.data_source.adapters import base

    engine = FakeEngine()
    captured = {}

    def fake_create_engine(*args, **kwargs):
        captured.update(kwargs)
        return engine

    monkeypatch.setattr(base, "create_engine", fake_create_engine)
    adapter = adapter_for("postgresql")

    adapter.test_connection(
        make_definition("postgresql"),
        DataSourceCredential("reader", "secret"),
        query_timeout=30,
    )

    assert captured["poolclass"] is NullPool
    assert engine.disposed is True
    assert engine.connection.executed == ["SELECT 1"]


def test_connect_args_use_bounded_platform_timeout():
    from app.core.data_source.adapters import adapter_for

    credential = DataSourceCredential("reader", "secret")
    postgresql = adapter_for("postgresql").connect_args(
        make_definition("postgresql"),
        credential,
        query_timeout=30,
    )
    mysql = adapter_for("mysql").connect_args(
        make_definition("mysql"),
        credential,
        query_timeout=30,
    )

    assert postgresql["options"] == "-c statement_timeout=30000"
    assert mysql["read_timeout"] == 30
    assert mysql["write_timeout"] == 30


def test_pooled_engine_uses_bounded_queue_pool_settings(monkeypatch):
    from app.core.data_source.adapters import adapter_for
    from app.core.data_source.adapters import base

    captured = {}
    engine = FakeEngine()

    def fake_create_engine(*args, **kwargs):
        captured.update(kwargs)
        return engine

    monkeypatch.setattr(base, "create_engine", fake_create_engine)
    adapter_for("postgresql").create_pooled_engine(
        make_definition("postgresql"),
        DataSourceCredential("reader", "secret"),
        {
            "pool_size": 2,
            "max_overflow": 3,
            "pool_timeout": 10,
            "pool_recycle": 1800,
            "query_timeout": 30,
        },
    )

    assert captured["pool_size"] == 2
    assert captured["max_overflow"] == 3
    assert captured["pool_timeout"] == 10
    assert captured["pool_recycle"] == 1800
    assert captured["pool_pre_ping"] is True
    assert captured["pool_use_lifo"] is True
    assert captured["pool_reset_on_return"] == "rollback"
