import { Database } from "./Database";
import { DatabaseError } from "./Error";
import type { SqliteDatabaseOptions } from "./Interface";
import { join, sep, extname } from 'path';
import { mkdirSync, existsSync } from 'fs';
import { get, set, unset } from 'lodash';

export class SqliteDatabase extends Database {
    private SQLQuery: any;
    private options: SqliteDatabaseOptions;
    public constructor(options: SqliteDatabaseOptions) {
        super();

        let notSQLQuery: any = undefined;
        this.options = options;
        try {
        notSQLQuery = require("better-sqlite3");
        } catch {
            throw new DatabaseError('The "better-sqlite3" module is not installed. Please install it with "npm install better-sqlite3"');
        }

        let paths = this.options.filePath.split(sep);
        let resolvedFilePath = process.cwd();

        for (const path of paths) {
            resolvedFilePath += `${sep}${path}`;

            if (!existsSync(resolvedFilePath) && extname(resolvedFilePath) !== '.db')
                mkdirSync(resolvedFilePath);
            else if (!existsSync(resolvedFilePath) && extname(resolvedFilePath) === '.db') {
                this.SQLQuery = notSQLQuery(resolvedFilePath);
                break;
            } else if (existsSync(resolvedFilePath) && extname(resolvedFilePath) === '.db') {
                this.SQLQuery = notSQLQuery(resolvedFilePath);
                break;
            }
        }

        this.SQLQuery.prepare(`CREATE TABLE IF NOT EXISTS ${options.table} (key TEXT, value TEXT)`).run();
    }

    public getAll() {
        return this.cache;
    }

    public get(key: string) {
        return get(this.cache, key);
    }

    public set(key: string, value: any) {
        let dotKey = key.split('.')[0];

        let data = get(this.cache, dotKey);

        set(this.cache, key, value);

        if(data) {
            if(key.includes('.')) {
                let data = get(this.cache, dotKey);
                this.SQLQuery.prepare(`UPDATE ${this.options.table} SET value = ? WHERE key = ?`).run(JSON.stringify(data), dotKey);
            } else {
                this.SQLQuery.prepare(`UPDATE ${this.options.table} SET value = ? WHERE key = ?`).run(JSON.stringify(value), key);
            }
        } else {
            if(key.includes('.')) {
                let data = get(this.cache, dotKey);
                this.SQLQuery.prepare(`INSERT INTO ${this.options.table} (key, value) VALUES (?, ?)`).run(dotKey, JSON.stringify(data));
            } else {
                this.SQLQuery.prepare(`INSERT INTO ${this.options.table} (key, value) VALUES (?, ?)`).run(key, JSON.stringify(value));
            }
        }

        return value;
    }


    public remove(key: string) {
        unset(this.cache, key);

        this.SQLQuery.prepare(`DELETE FROM ${this.options.table} WHERE key = ?`).run(key);
    }

    public add(key: string, value: number) {
        let data = this.get(key);

        if (typeof data !== 'number') throw new DatabaseError(`Cannot add "${value}" to "${key}" because "${key}" is not a number.`);

        data += value;

        this.set(key, data);

        return data;
    }

    public push(key: string, value: any) {
        let data = this.get(key);

        if (!Array.isArray(data)) throw new DatabaseError(`Cannot push "${value}" to "${key}" because "${key}" is not an array.`);

        data.push(value);

        this.set(key, data);

        return data;
    }

    public pull(key: string, value: any) {
        let data = this.get(key);

        if (!Array.isArray(data)) throw new DatabaseError(`Cannot pull "${value}" from "${key}" because "${key}" is not an array.`);

        data = data.filter((v: any) => v !== value);

        this.set(key, data);

        return data;
    }

    public clear() {
        this.cache = {};

        this.SQLQuery.prepare(`DELETE FROM ${this.options.table}`).run();
    }

    public close() {
        this.SQLQuery.close();
    }

    public async init() {
        let data = this.SQLQuery.prepare(`SELECT * FROM ${this.options.table}`).all();

        for (const row of data) {
            this.cache[row.key] = JSON.parse(row.value);
        }
    }

    public async save() {
        for (const key in this.cache) {
            let data = this.cache[key];

            if (typeof data === 'object') data = JSON.stringify(data);

            this.SQLQuery.prepare(`UPDATE ${this.options.table} SET value = ? WHERE key = ?`).run(data, key);
        }
    }

    public async destroy() {
        this.SQLQuery.prepare(`DROP TABLE ${this.options.table}`).run();
    }

    public async delete() {
        this.SQLQuery.close();
        this.SQLQuery = undefined;
    }



}