DEFAULT_LIMIT = 30
import asyncio
import typing

from snek.system.model import BaseModel


class BaseMapper:

    model_class: BaseModel = None
    default_limit: int = DEFAULT_LIMIT
    table_name: str = None
    semaphore = asyncio.Semaphore(1)
    
    def __init__(self, app):
        self.app = app

        self.default_limit = self.__class__.default_limit

    @property
    def db(self):
        return self.app.db

    @property
    def loop(self):
        return asyncio.get_event_loop()

    async def run_in_executor(self, func, *args, **kwargs):
        use_semaphore = kwargs.pop("use_semaphore", False)
        
        def _execute(): 
            result = func(*args, **kwargs)
            if use_semaphore:
                self.db.commit()
            return result 
        
        return _execute()
        #async with self.semaphore:
        #    return await self.loop.run_in_executor(None, _execute)


    async def new(self):
        return self.model_class(mapper=self, app=self.app)

    @property
    def table(self):
        return self.db[self.table_name]

    async def get(self, uid: str = None, **kwargs) -> BaseModel:
        if uid:
            kwargs["uid"] = uid
        if not kwargs.get("deleted_at"):
            kwargs["deleted_at"] = None
        record = await self.run_in_executor(self.table.find_one, **kwargs)
        if not record:
            return None
        record = dict(record)
        model = await self.new()
        for key, value in record.items():
            model[key] = value
        return model

    async def exists(self, **kwargs):
        return await self.run_in_executor(self.table.exists, **kwargs)

    async def count(self, **kwargs) -> int:
        return await self.run_in_executor(self.table.count, **kwargs)

    async def save(self, model: BaseModel) -> bool:
        if not model.record.get("uid"):
            raise Exception(f"Attempt to save without uid: {model.record}.")
        model.updated_at.update()
        return await self.run_in_executor(self.table.upsert, model.record, ["uid"],use_semaphore=True)

    async def find(self, **kwargs) -> typing.AsyncGenerator:
        if not kwargs.get("_limit"):
            kwargs["_limit"] = self.default_limit
        if not kwargs.get("deleted_at"):
            kwargs["deleted_at"] = None
        for record in await self.run_in_executor(self.table.find, **kwargs):
            model = await self.new()
            for key, value in record.items():
                model[key] = value
            yield model

    async def _use_semaphore(self, sql):
        sql = sql.lower().strip()
        return "insert" in sql or "update" in sql or "delete" in sql

    async def query(self, sql, *args):
        for record in await self.run_in_executor(self.db.query, sql, *args, use_semaphore=await self._use_semaphore(sql)):
            yield dict(record)

    async def update(self, model):
        if not model["deleted_at"] is None:
            raise Exception("Can't update deleted record.")
        model.updated_at.update()
        return await self.run_in_executor(self.table.update, model.record, ["uid"],use_semaphore=True)

    async def upsert(self, model):
        model.updated_at.update()
        return await self.run_in_executor(self.table.upsert, model.record, ["uid"],use_semaphore=True)

    async def delete(self, **kwargs) -> int:
        if not kwargs or not isinstance(kwargs, dict):
            raise Exception("Can't execute delete with no filter.")
        kwargs["use_semaphore"] = True
        return await self.run_in_executor(self.table.delete, **kwargs)
