import {Injectable, Model, Field, FieldType, Collection, ErrorWithContext, make, Application} from '@extollo/lib'
import { JSONString, Settings } from '../types';

export class MissingRequiredSettingError extends ErrorWithContext {
    constructor(key: string) {
        super(`Missing required setting: ${key}`, { key })
    }
}

/**
 * Setting Model
 * -----------------------------------
 * Helper for loading various one-off settings.
 */
@Injectable()
export class Setting<Key extends keyof Settings> extends Model<Setting<Key>> {
    protected static table = 'p5x_settings'
    protected static key = 'id'

    public static load<Key extends keyof Settings>(key: Key): Promise<Collection<Settings[Key]>> {
        return Setting.query<Setting<Key>>()
            .where('setting_key', '=', key)
            .get()
            .map(x => x.value)
    }

    public static async loadRequired<Key extends keyof Settings>(key: Key): Promise<Collection<Settings[Key]>> {
        const load = await this.load(key)
        if ( load.isEmpty() ) {
            throw new MissingRequiredSettingError(key)
        }
        return load
    }

    public static loadOneRequired<Key extends keyof Settings>(key: Key): Promise<Settings[Key]> {
        return this.loadRequired(key).then(x => x.first()!)
    }

    public static async set<Key extends keyof Settings>(key: Key, value: Settings[Key]): Promise<void> {
        // Delete any existing records w/ this key
        await Setting.query().where('settingKey', '=', key).delete()

        // Insert a new one
        const setting = Application.getContainer().makeNew<Setting<Key>>(Setting);
        setting.settingKey = key
        setting.value = value
        await setting.save()
    }

    public static async add<Key extends keyof Settings>(key: Key, value: Settings[Key]): Promise<void> {
        // Delete any existing records w/ this key and value
        await Setting.query()
            .where('settingKey', '=', key)
            .where('settingValue', '=', JSON.stringify(value))
            .delete()

        // Insert a new one
        const setting = new Setting<Key>;
        setting.settingKey = key
        setting.value = value
        await setting.save()
    }

    @Field(FieldType.serial)
    protected id?: number

    @Field(FieldType.varchar, 'setting_key')
    protected settingKey!: Key

    @Field(FieldType.text, 'setting_json')
    protected settingJson!: JSONString

    public get value(): Settings[Key] {
        return JSON.parse(this.settingJson)
    }

    public set value(v: Settings[Key]) {
        this.settingJson = JSON.stringify(v) as JSONString
    }
}
