import typing
import asyncio
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("sqlite")

import aiosqlite

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


if(TYPE_CHECKING):
    from supersql.core.query import Query

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


class EngineConstructorArgs(typing.TypedDict):
    use_ssl: bool


class Engine(IEngine):
    def __init__(self, query, **kwargs: EngineConstructorArgs):
        self._query = query
        self._config = kwargs
        self._url = kwargs.get('database')
        
        if not self._url and query:
             self._url = query._database

        self.pool = None
    
    async def connect(self) -> None:
        if self.pool is not None:
            raise RuntimeError(CONNECTED_MESSAGE)
        logger.debug(f"Initializing SQLite pool for {self._url}")
        self.pool = Pool(self._url, **self._config)

    async def disconnect(self) -> None:
        if self.pool is None:
            raise RuntimeError(DISCONNECTED_MESSAGE)
        logger.debug("Closing SQLite pool")
        await self.pool.close()
        self.pool = None

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

    async def execute_query(self, query: 'Query') -> typing.Any:
        connection = await self.pool.acquire()
        try:
            sql = query.build()
            if query._unsafe:
                async with connection.execute(sql) as cursor:
                    if query._consequence == 'DQL':
                        return await cursor.fetchall()
                    return cursor.rowcount
            else:
                async with connection.execute(sql, query.args) as cursor:
                    if query._consequence == 'DQL':
                        return await cursor.fetchall()
                    return cursor.rowcount
        finally:
            await self.pool.release(connection)

    def connection(self) -> "Connection":
        # Issue 44: Simple check to avoid recreating if exists, though strictly 
        # not thread-safe without locks in a threaded env. In asyncio single-thread,
        # this is atomic.
        if self.pool is None: self.pool = Pool(self._url, **self._config)
        return Connection(self.pool)
    

class Pool(object):
    def __init__(self, url: str, **kwargs: typing.Any):
        self._url = url
        self._config = kwargs
        self._min_size = kwargs.get('pool_min_size', 10)
        self._max_size = kwargs.get('pool_max_size', 10)
        self._timeout = kwargs.get('pool_timeout', 60)
        
        self._queue = asyncio.Queue(maxsize=self._max_size)
        self._current_size = 0
        self._lock = asyncio.Lock()
        logger.debug(f"SQLite Pool initialized with min={self._min_size}, max={self._max_size}")

    async def acquire(self) -> aiosqlite.Connection:
        try:
            connection = self._queue.get_nowait()
            logger.debug("Acquired SQLite connection from pool (idle)")
            return connection
        except asyncio.QueueEmpty:
            pass

        async with self._lock:
            if self._current_size < self._max_size:
                self._current_size += 1
                try:
                    invalid_args = {'database', 'host', 'port', 'user', 'password'}
                    connect_kwargs = {
                        k: v for k, v in self._config.items() 
                        if not k.startswith('pool_') and k not in invalid_args
                    }
                    connection = aiosqlite.connect(self._url, isolation_level=None, **connect_kwargs)
                    await connection.__aenter__()
                    logger.debug("Created new SQLite connection for pool")
                    return connection
                except Exception as e:
                    self._current_size -= 1
                    logger.error(f"Failed to create SQLite connection: {e}")
                    raise
        
        try:
            logger.debug("Waiting for SQLite connection from pool...")
            conn = await asyncio.wait_for(self._queue.get(), timeout=self._timeout)
            logger.debug("Acquired released SQLite connection")
            return conn
        except asyncio.TimeoutError:
            logger.error(f"Timeout waiting for SQLite connection (timeout={self._timeout}s)")
            raise TimeoutError(f"Timed out waiting for connection from pool (timeout={self._timeout}s)")

    async def release(self, connection: aiosqlite.Connection) -> None:
        if connection:
            async with self._lock:
                try:
                    self._queue.put_nowait(connection)
                    logger.debug("Released SQLite connection back to pool")
                except asyncio.QueueFull:
                    await connection.__aexit__(None, None, None)
                    self._current_size -= 1
                    logger.warning("Closed overflow SQLite connection")

    async def close(self) -> None:
        async with self._lock:
            count = 0
            while not self._queue.empty():
                try:
                    conn = self._queue.get_nowait()
                    await conn.__aexit__(None, None, None)
                    count += 1
                except Exception as e:
                    logger.warning(f"Error closing SQLite connection: {e}")
            outstanding = self._current_size - count
            self._current_size = outstanding
            if outstanding > 0:
                logger.warning(f"{outstanding} SQLite connections still in use during pool close")
            logger.debug(f"Closed {count} idle SQLite connections in pool")


class Connection(IConnection):
    def __init__(self, pool: Pool):
        self._pool = pool
        self._connection: typing.Union[None, aiosqlite.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.execute(query.build(), query.args) as cursor:
            if query._consequence == 'DQL':
                return await cursor.fetchall()
            return cursor.rowcount

    @disconnectable
    async def fetchall(self, query: 'Query') -> typing.List[typing.Mapping]:
        self._connection.row_factory = aiosqlite.Row
        async with self._connection.execute(query.build(), query.args) as cursor:
            results = await cursor.fetchall()
            return results

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

    @disconnectable
    async def fetchmany(self, query: 'Query', limit: int) -> typing.Any:
        self._connection.row_factory = aiosqlite.Row
        async with self._connection.execute(query.build(), query.args) as cursor:
            results = await cursor.fetchmany(limit)
            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.execute(query.build(), query.args) as cursor:
            return cursor.rowcount
