import sqlite3
import threading
import time
from typing import Type
from .models import (
    Select,
    Update,
    Insert,
    Delete,
    Create,
    BaseRequest,
    Table,
    TableModel,
    WhereFilter,
    ExactFilter
)

mapping = {
    "int" : "INTEGER",
    "float" : "REAL",
    "str" : "TEXT",
    "bytes" : "BLOB",
    "NoneType" : "TEXT"
}

class Database:
    
    def __init__(self, path: str, looptime: int = 0.01, autorun: bool = True, additional_mapping: dict[str, str] = {}, logging: bool = False, auto_database_mapping: bool = True):
        self.path = path if path.endswith(".odb") else path + ".odb"
        self.database = sqlite3.connect(path, check_same_thread=False)
        self.cursor = self.database.cursor()
        self.queue = []
        self.results = {}
        self.thread = None
        self.running = False
        self.looptime = looptime
        self.logging = logging
        self.tablemapping = {}
        self.dbmapping = auto_database_mapping
        
        self.mapping = mapping.copy()
        self.mapping.update(additional_mapping)
        
        if autorun: self._run()
    
    def select(self, table: Type[Table] | str, where: list[WhereFilter] = [], exclude: list[WhereFilter] = []):
        table = table if isinstance(table, Table) else self.tablemapping[table]
        return self.process(Select(table=table, where=where, exclude=exclude))
    
    def insert(self, table: Type[Table] | str, data: Type[TableModel]):
        table = table if isinstance(table, Table) else self.tablemapping[table]
        return self.process(Insert(table=table, data=data))
    
    def update(self, table: Type[Table] | str, data: Type[TableModel], where: list[WhereFilter]):
        table = table if isinstance(table, Table) else self.tablemapping[table]
        return self.process(Update(table=table, data=data, where=where))
    
    def delete(self, table: Type[Table] | str, where: list[WhereFilter]):
        table = table if isinstance(table, Table) else self.tablemapping[table]
        return self.process(Delete(table=table, where=where))
    
    def create(self, table: Type[Table] | str):
        table = table if isinstance(table, Table) else self.tablemapping[table]
        return self.process(Create(table=table))
    
    def exists(self, table: Type[Table] | str, filter: ExactFilter):
        table = table if isinstance(table, Table) else self.tablemapping[table]
        return filter.modeldata in self.select(table)
    
    def updateTableMapping(self, table: Type[Table]):
        self.tablemapping[table.name] = table
    
    def isDone(self, rid: str):
        return rid in self.results
    
    def getResult(self, rid: str):
        return self.results.pop(rid) if self.isDone(rid) else None
    
    def addRequest(self, request: Type[BaseRequest]):
        self.queue.append(request)
    
    def process(self, request: Type[BaseRequest]):
        self.addRequest(request)
        
        while not self.isDone(request.rid):
            pass
        
        return self.getResult(request.rid)
    
    def _run(self):
        if not self.running:
            self.running = True
            self.thread = threading.Thread(target=self._loop)
            self.thread.start()
            return True
        return False
    
    def _stop(self):
        if self.running:
            self.running = False
            self.thread.join()
            self.thread = None
            return True
        return False
    
    def _loop(self):
        while True:
            if not self.running: break
            
            if len(self.queue) > 0:
                request = self.queue.pop(0)
                
                if isinstance(request, Select):
                    
                    if self.dbmapping and request.table.name not in self.tablemapping:
                        self.tablemapping[request.table.name] = request.table
                    
                    self.cursor.execute(f"SELECT * FROM {request.table.name} WHERE {' AND '.join([str(item.field)+' = '+'?' for item in request.where])}", tuple(list([item.value for item in request.where])))
                    res = self.cursor.fetchall()
                    
                    objs = []
                    for row in res:
                        obj = request.table.model(**dict(zip(request.table.model.model_fields.keys(), row)))
                        
                        for exclude in request.exclude:
                            delattr(obj, exclude.field)
                        
                        objs.append(obj)
                    
                    self.results[request.rid] = objs
                
                elif isinstance(request, Insert):
                    
                    if self.dbmapping and request.table.name not in self.tablemapping:
                        self.tablemapping[request.table.name] = request.table
                    
                    rows = [request.data.__dict__[item] for item in request.data.model_fields.keys()]
                    keys = request.data.model_fields.keys()
                    self.cursor.execute(f"INSERT INTO {request.table.name} ({', '.join(keys)}) VALUES ({', '.join(['?']*len(rows))})", rows)
                    self.database.commit()
                
                elif isinstance(request, Update):
                    
                    if self.dbmapping and request.table.name not in self.tablemapping:
                        self.tablemapping[request.table.name] = request.table
                    
                    rows = request.data.model_fields.values()
                    self.cursor.execute(f"UPDATE {request.table.name} SET ({', '.join([str(k)+' = '+str(v) for k, v in request.table.model.model_fields.items()])}) WHERE {' AND '.join([str(item.field)+' = '+'?' for item in request.where])}", tuple(list([item.value for item in request.where])))
                    self.database.commit()
                
                elif isinstance(request, Delete):
                    
                    if self.dbmapping and request.table.name not in self.tablemapping:
                        self.tablemapping[request.table.name] = request.table
                    
                    self.cursor.execute(f"DELETE FROM {request.table.name} WHERE {' AND '.join([str(item.field)+' = '+'?' for item in request.where])}", tuple(list([item.value for item in request.where])))
                    self.database.commit()
                
                elif isinstance(request, Create):
                    
                    if self.dbmapping and request.table.name not in self.tablemapping:
                        self.tablemapping[request.table.name] = request.table
                    
                    self.cursor.execute(f"CREATE TABLE IF NOT EXISTS {request.table.name} ({', '.join([str(k)+' '+self.mapping[v.annotation.__name__] for k, v in request.table.model.model_fields.items()])})")
                    self.database.commit()
                
                else:
                    raise Exception(f"Unknown request method! ({request.__class__.__name__})")
                
                if not self.isDone(request.rid):
                    self.results[request.rid] = True
                
            time.sleep(self.looptime)