/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-explicit-any,prefer-destructuring */
import {
  RepositoryBase,
  recordInfo,
  UUID,
  Flavor,
} from "@digiresilience/metamigo-common";

export type SettingId = Flavor<UUID, "Setting Id">;

export interface UnsavedSetting<T> {
  name: string;
  value: T;
}

export interface SavedSetting<T> extends UnsavedSetting<T> {
  id: SettingId;
  createdAt: Date;
  updatedAt: Date;
}

export const SettingRecord = recordInfo<UnsavedSetting<any>, SavedSetting<any>>(
  "app_public",
  "settings"
);

export class SettingRecordRepository extends RepositoryBase(SettingRecord) {
  async findByName<T>(name: string): Promise<SavedSetting<T> | null> {
    return this.db.oneOrNone("SELECT * FROM $1 $2:raw LIMIT 1", [
      this.schemaTable,
      this.where({ name }),
    ]);
  }

  async upsert<T>(name: string, value: T): Promise<SavedSetting<T>> {
    return this.db.one(
      `INSERT INTO $1 ($2:name) VALUES ($2:csv)
       ON CONFLICT (name)
       DO UPDATE SET value = EXCLUDED.value RETURNING *`,
      [this.schemaTable, this.columnize({ name, value })]
    );
  }
}

// these helpers let us create type safe setting constants
export interface SettingType<T = any> {
  _type: T;
}

export interface SettingInfo<T = any> extends SettingType<T> {
  name: string;
}

export function castToSettingInfo(
  runtimeData: Omit<SettingInfo, "_type">
): SettingInfo {
  return runtimeData as SettingInfo;
}

export function settingInfo<T>(name: string): SettingInfo<T>;

// don't use this signature, use the explicit typed signature
export function settingInfo(name: string) {
  return castToSettingInfo({
    name,
  });
}

export interface ISettingsService {
  name: string;
  lookup<T>(settingInfo: SettingInfo<T>): Promise<T>;
  save<T>(settingInfo: SettingInfo<T>, value: T): Promise<T>;
}

export const SettingsService = (
  repo: SettingRecordRepository
): ISettingsService => ({
  name: "settingService",
  async lookup<T>(settingInfo: SettingInfo<T>): Promise<T> {
    const s = await repo.findByName<T>(settingInfo.name);
    return s.value;
  },

  async save<T>(settingInfo: SettingInfo<T>, value: T): Promise<T> {
    const s = await repo.upsert(settingInfo.name, value);
    return s.value;
  },
});

const _test = async () => {
  // here is an example of how to use this module
  // it also serves as a compile-time test case
  const repo = new SettingRecordRepository({} as any);

  // create your own custom setting types!
  // the value is serialized as json in the database
  type Custom = { foo: string; bar: string };
  type CustomUnsavedSetting = UnsavedSetting<Custom>;
  type CustomSetting = SavedSetting<Custom>;

  const s3: CustomSetting = await repo.findByName("test");

  const customValue = { foo: "monkeys", bar: "eggplants" };
  let customSetting = { name: "custom", value: customValue };
  customSetting = await repo.insert(customSetting);
  const value: Custom = customSetting.value;

  const MySetting = settingInfo<string>("my-setting");
};
