import type { Compilable, InferResult, Kysely } from 'kysely';

type QueryResultSuccess<T> = {
  success: true;
  data: T;
};
type QueryResultError = {
  success: false;
  errorMsg: string;
};
type QueryResult<T> = QueryResultSuccess<T> | QueryResultError;
type AsyncQueryResult<T> = Promise<QueryResult<T>>;

type DB = {
  product: {
    id: number;
    name: string;
  };
};

class Datasource<TDatabase = DB> {
  constructor(public kysely: Kysely<TDatabase>) {}

  query = async <TFn extends () => any, TData = ReturnType<TFn>>(
    fn: TFn
  ): AsyncQueryResult<TData> => {
    return this.createQueryResultSuccess<TData>(fn());
  };

  queryKysely = async <
    TQuery extends Compilable<unknown>,
    TData = InferResult<TQuery>,
  >(
    query: TQuery
  ): AsyncQueryResult<TData> => {
    try {
      const r = await this.kysely.executeQuery(query);
      return this.createQueryResultSuccess<TData>(r.rows as TData);
    } catch (err) {
      return this.createQueryResultError(err as Error);
    }
  };

  protected createQueryResultSuccess = <T>(data: T): QueryResultSuccess<T> => {
    return {
      success: true,
      data: data,
    };
  };
  protected createQueryResultError = (err: Error): QueryResultError => {
    return {
      success: false,
      errorMsg: err.message,
    };
  };
}

type Product = {
  id: number;
  name: string;
  description: string;
};

class Repo {
  constructor(private ds: Datasource) {}
  search = async (): AsyncQueryResult<Product[]> => {
    // ts-expect-error shoudl give an error cause id aren't the same types

    type IncompleteProduct = {
      id: number;
      name: string;
    };
    const incompleteProduct: IncompleteProduct = {
      id: 1,
      name: 'Incomplete Product',
    };

    const getIncompleteProducts = (): IncompleteProduct[] => {
      return [incompleteProduct];
    };

    return this.ds.query(getIncompleteProducts);

    const a = await this.ds.query(getIncompleteProducts);
    if (a.success) {
      const _z = a.data[0]?.id;
    }

    return a;
  };
  searchKysely = async (): AsyncQueryResult<Product[]> => {
    const query = this.ds.kysely.selectFrom('product').select(['id', 'name']);
    const a = await this.ds.queryKysely(query);
    return a;

    // return await this.ds.queryKysely(query);
    // return this.ds.queryKysely(query);
  };
}

const kysely = {} as unknown as Kysely<DB>;
const repo = new Repo(new Datasource(kysely));

const testTypes = async () => {
  const result = await repo.search();
  const _idOrErrMessage = result.success
    ? result.data[0]?.id
    : result.error.messsage;
  const result2 = await repo.searchKysely();
  const _idOrErrMessage2 = result2.success
    ? result2.data[0]?.id
    : result2.error.messsage;
};