'''
Module provids Utilities to Interface with Database Model

Includes:
    - DateTime De-/Encoder for JSON loads/dumps
    - Database Initialize Helper
    - Module Class Summarizer
'''


import sys, inspect, json 
from datetime import datetime, date
from pathlib import Path
from playhouse.shortcuts import model_to_dict, dict_to_model
from .model import *

class DateTimeEncoder(json.JSONEncoder):
    '''
    Helper Class converting datetime.datetime -> isoformated String
    '''


    def default(self, obj):
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()

# Predefined used timestamp keys
KEYNAMES = [
    "date", "timestamp", "last_updatet", "last_created", "last_editet", "created_at"
]

def DateTimeDecoder(obj: dict) -> dict:
    '''
    Helper Function converting isoformated String -> datetime.datetime
    '''


    for key, value in obj.items():
        if key not in KEYNAMES:
            continue
        try:
            obj[key] = datetime.fromisoformat(value)
        except ValueError:
            pass
    return obj
   

def get_module_cls(module: str) -> tuple[tuple[str, object]]:
    '''
    Given a module name function returns a list of Classes defined only in that Module
    '''


    assert type(module) is str, "Provided Module isn't a String"
    members = inspect.getmembers(sys.modules[module], inspect.isclass)
    return tuple(member for member in members if member[1].__module__ == module)

# precalculated from model.py 
tables = tuple(table[1] for table in get_module_cls('dbmodel.model') if not table[1] is BaseModel)
table_labels = tuple(table[0] for table in get_module_cls('dbmodel.model') if not table[1] is BaseModel)

def init_db(name: Path | str) -> None:
    '''
    (Creates,) Connects and Initializes the db descriptor from a given *.db file 
    '''


    assert isinstance(name, (Path, str)), "Provided Name isn't of type Path | str"
    
    # convert to String
    if type(name) is Path:
        name = str(name)
    
    # Switch Database; Initialize if needed
    if not db.is_closed():
        db.close()
    db.init(name)
    db.connect()
    db.create_tables(tables)  # Ensure tables exist


def save_as_json(filename: str, path: Path | str = Path('.')) -> Path:
    '''
    Saves the current loaded Database as <filename>.json to given <path>

    JSON Format:
        {
            <Tablename>: [
                {<Tableparam>: <Value>, ...}
            ],
            ...
        }
    '''


    assert type(filename) is str, "Provided Filename isn't a String"
    assert isinstance(path, Path | str), "Provided Path isn't of type Path | str"

    # Convert given path to Path
    if type(path) is str:
        path = Path(path)
    
    filename = Path(filename)

    # Set Correct Suffix
    if filename.suffix != '.json':
        filename = filename.with_suffix('.json')

    file = path.resolve().absolute() / filename
    
    # dump db
    database = {
        table.__name__: list(table.select().dicts())
        for table in tables
    }
    
    # db -> json
    with open(file, "w") as fp:
        json.dump(database, fp, cls=DateTimeEncoder)

    return file

def create_from_json(dbname: str, file: Path | str) -> Path:
    '''
    Creates a new Database <dbname>.db in from given <file>

    Valid JSON Format:
        {
            <Tablename>: [
                {<Tableparam>: <Value>, ...}
            ],
            ...
        }
    '''

    assert type(dbname) is str, "Provided Database name isn't a String"
    assert isinstance(file, (Path | str)), "Provided file descriptor isn't of type Path | str"

    # Convert file to Path 
    if type(file) is str:
        file = Path(file)
    assert file.suffix == '.json', "File isn't a JSON"
    
    # Set correct Suffix and connect
    dbname = Path(dbname).resolve().absolute()
    if dbname.suffix != '.db':
        dbname = dbname.with_suffix('.db')
    init_db(dbname)

    # load from json 
    with open(file, "r") as fp:
        data = json.load(fp, object_hook=DateTimeDecoder)
    
    assert all([keys == table.__name__ for keys, table in zip(data.keys(), tables)]), f"{file.name} can't be convert to Database"

    # Insert Data 
    for k, v in data.items():
        if not v:
            continue

        table = next((table for table in tables if table.__name__ == k))
        table.insert_many(v).execute()
    
    return dbname 

if __name__ == "__main__":
    init_db('/home/phil/programming/grapher/assets/WiSe_24_25.db')
    f = save_as_json("file")
    print(f)
    print(create_from_json("test", f))



