import typing
import logging
from typing import TYPE_CHECKING

from supersql.engines.connection import IConnection, IEngine, connectable, disconnectable
from supersql.utils.vendor_deps import validate_vendor_dependencies

# Validate dependencies at module level
validate_vendor_dependencies("mssql")

# Import SQL Server dependencies after validation
import aioodbc

logger = logging.getLogger('supersql.engines.mssql')

if TYPE_CHECKING:
    from supersql.core.query import Query

CONNECTED_MESSAGE = "A connection already exists"
DISCONNECTED_MESSAGE = "No connection found to database"

USER, PASSWORD, HOST, PORT, DATABASE = 'user', 'password', 'host', 'port', 'database'


class EngineConstructorArgs(typing.TypedDict):
    user: str
    password: str
    port: int
    database: str
    host: str
    driver: str
    use_ssl: bool


def _build_connection_string(config: EngineConstructorArgs) -> str:
    """Build SQL Server connection string from config."""
    driver = config.get('driver', 'ODBC Driver 17 for SQL Server')
    host = config['host']
    port = config.get('port', 1433)
    database = config['database']
    user = config['user']
    password = config['password']
    
    conn_str = (
        f"DRIVER={{{driver}}};"
        f"SERVER={host},{port};"
        f"DATABASE={database};"
        f"UID={user};"
        f"PWD={password};"
    )
    
    if config.get('use_ssl', True):
        conn_str += "Encrypt=yes;TrustServerCertificate=no;"
    
    return conn_str


class Engine(IEngine):
    def __init__(self, query, **kwargs: EngineConstructorArgs):
        self._query = query
        self._config = kwargs
        self.pool = None

    @property
    def parameter_placeholder(self) -> str:
        return "?"

    async def connect(self) -> None:
        if self.pool is not None:
            raise RuntimeError(CONNECTED_MESSAGE)
        logger.debug("Creating MSSQL connection pool")
        conn_str = _build_connection_string(self._config)
        try:
            self.pool = await aioodbc.create_pool(dsn=conn_str)
            logger.debug("MSSQL connection pool created successfully")
        except Exception as e:
            logger.error(f"Failed to create MSSQL connection pool: {e}")
            raise RuntimeError("Failed to connect to MSSQL database") from e

    async def disconnect(self) -> None:
        if self.pool is None:
            raise RuntimeError(DISCONNECTED_MESSAGE)
        logger.debug("Closing MSSQL connection pool")
        try:
            self.pool.close()
            await self.pool.wait_closed()
            self.pool = None
            logger.debug("MSSQL connection pool closed")
        except Exception as e:
            logger.error(f"Failed to close MSSQL connection pool: {e}")
            raise

    async def execute_query(self, query: 'Query') -> typing.Any:
        async with self.pool.acquire() as conn:
            async with conn.cursor() as cursor:
                sql = query.build()
                if query._unsafe:
                    await cursor.execute(sql)
                else:
                    await cursor.execute(sql, query.args)
                if query._consequence == 'DQL':
                    columns = [column[0] for column in cursor.description]
                    results = []
                    async for row in cursor:
                        results.append(dict(zip(columns, row)))
                    return results
                else:
                    return cursor.rowcount

    def connection(self) -> "Connection":
        return Connection(self.pool)


class Connection(IConnection):
    def __init__(self, pool):
        self._pool = pool
        self._connection = None

    @connectable
    async def begin(self) -> None:
        self._connection = await self._pool.acquire()

    @disconnectable
    async def done(self) -> None:
        await self._pool.release(self._connection)
        self._connection = None

    @disconnectable
    async def execute(self, query: 'Query') -> typing.Any:
        async with self._connection.cursor() as cursor:
            await cursor.execute(query.build(), query.args)
            return cursor.rowcount

    @disconnectable
    async def executemany(self, queries: typing.List['Query']) -> typing.Any:
        async with self._connection.cursor() as cursor:
            for query in queries:
                await cursor.execute(query.build(), query.args)
            return cursor.rowcount

    @disconnectable
    async def fetchall(self, query: "Query") -> typing.List[typing.Mapping]:
        async with self._connection.cursor() as cursor:
            await cursor.execute(query.build(), query.args)
            columns = [column[0] for column in cursor.description]
            results = []
            async for row in cursor:
                results.append(dict(zip(columns, row)))
            return results

    @disconnectable
    async def fetchmany(self, query: 'Query', limit: int) -> typing.Any:
        async with self._connection.cursor() as cursor:
            await cursor.execute(query.build(), query.args)
            columns = [column[0] for column in cursor.description]
            results = []
            count = 0
            async for row in cursor:
                if count >= limit:
                    break
                results.append(dict(zip(columns, row)))
                count += 1
            return results

    @disconnectable
    async def release(self) -> None:
        if self._connection:
            await self._pool.release(self._connection)
            self._connection = None

    @disconnectable
    async def rowcount(self, query: 'Query') -> int:
        async with self._connection.cursor() as cursor:
            await cursor.execute(query.build(), query.args)
            return cursor.rowcount
