/* eslint-disable @typescript-eslint/no-unused-vars */
import { ICollection, IQueryCriteria, UniqueID } from "@shared/contexts";
import { ModelDefined, Transaction } from "sequelize";

import { IRepository } from "../../domain/repositories";
import { ISequelizeAdapter } from "./SequelizeAdapter";

export abstract class SequelizeRepository<T> implements IRepository<T> {
  protected _transaction: Transaction;
  protected _adapter: ISequelizeAdapter;

  public constructor(props: { adapter: ISequelizeAdapter; transaction: Transaction }) {
    this._adapter = props.adapter;
    this._transaction = props.transaction;
  }

  protected getById(id: UniqueID): Promise<T | null> {
    throw new Error("[SequelizeRepository] getById not implemented!");
  }

  /*protected getBy(field: string, value: any): Promise<T> {
        throw new Error('[SequelizeRepository] getBy not implemented!');
    }*/

  protected findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<T>> {
    throw new Error("[SequelizeRepository] findAll not implemented!");
  }

  /*protected totalCount(queryCriteria?: IQueryCriteria): Promise<number> {
        throw new Error('[SequelizeRepository] totalCount not implemented!');
    }*/

  protected save(t: T): Promise<void> {
    throw new Error("[SequelizeRepository] save not implemented!");
  }

  /* protected remove(t: T): Promise<boolean> {
        throw new Error('[SequelizeRepository] remove not implemented!');
    }*/

  protected removeById(id: UniqueID): Promise<void> {
    throw new Error("[SequelizeRepository] removeById not implemented!");
  }

  protected async _getBy(
    modelName: string,
    field: string,
    value: any,
    params: any = {}
  ): Promise<T> {
    const _model = this._adapter.getModel(modelName);
    const where: { [key: string]: any } = {};

    where[field] = value;

    return _model.findOne({
      where,
      transaction: this._transaction,
      ...params,
    });
  }

  protected async _getById(modelName: string, id: UniqueID | string, params: any = {}): Promise<T> {
    const _model = this._adapter.getModel(modelName);
    return _model.findByPk(id.toString(), {
      transaction: this._transaction,
      ...params,
    });
  }

  protected async _findAll(
    modelName: string,
    queryCriteria?: IQueryCriteria,
    params: any = {}
  ): Promise<{ rows: any[]; count: number }> {
    console.time("_findAll");

    const { model: _model, query } = this._adapter.queryBuilder.generateQuery({
      model: this._adapter.getModel(modelName),
      queryCriteria,
    });

    const args = {
      ...query,
      distinct: true,
      transaction: this._transaction,
      ...params,
    };

    console.log(args.where);

    const result = _model.findAndCountAll(args);

    console.timeEnd("_findAll");

    return result;
  }

  protected async _exists(
    modelName: string,
    field: string,
    value: any,
    params: any = {}
  ): Promise<boolean> {
    const _model = this._adapter.getModel(modelName) as ModelDefined<any, any>;
    const where = {};
    where[field] = value;

    const count: number = await _model.count({
      where,
      transaction: this._transaction,
    });

    return Promise.resolve(Boolean(count !== 0));
  }

  protected async _save(
    modelName: string,
    id: UniqueID,
    data: any,
    params: any = {}
  ): Promise<void> {
    const _model = this._adapter.getModel(modelName);

    if (await this._exists(modelName, "id", id.toPrimitive())) {
      await _model.update(
        {
          ...data,
          id: undefined,
        },
        {
          where: { id: id.toPrimitive() },
          transaction: this._transaction,
          ...params,
        }
      );
    } else {
      await _model.create(
        {
          ...data,
          id: id.toPrimitive(),
        },
        {
          include: [{ all: true }],
          transaction: this._transaction,
          ...params,
        }
      );
    }
  }

  protected async _removeById(
    modelName: string,
    id: UniqueID,
    force: boolean = false,
    params: any = {}
  ): Promise<void> {
    const model: ModelDefined<any, any> = this._adapter.getModel(modelName);

    await model.destroy({
      where: {
        id: id.toPrimitive(),
      },
      transaction: this._transaction,
      force,
    });
  }

  /*protected _totalCount(
        modelName: string,
        queryCriteria?: IQueryCriteria
    ): Promise<number> {
        const { model: _model, query } = this.queryBuilder.generateQuery({
            model: this.adapter.getModel(modelName),
            queryCriteria,
        });

        return _model.count({
            distinct: true,
            ...query,
        });
    }

    
    
    protected async _removeByIds(
        modelName: string,
        ids: UniqueID[] | string[]
    ): Promise<boolean> {
        const _ids = ids.map((id: UniqueID | string) => id.toPrimitive());

        const destroyedRows: Promise<number> = await this.adapter.models[
            modelName
        ].destroy({
            where: {
                id: {
                    [Op.in]: _ids,
                },
            },
        });

        return !!destroyedRows;
    }

    */

  /*
		protected _debugModelInfo(model) {
		if (!model.name)
			return;

		console.log("\n\n----------------------------------\n",
			model.name,
			"\n----------------------------------");

		console.log("\nAttributes");
		console.log("\n----------------------------------\n");
		if (model._options.attributes) {
			model._options.attributes.forEach(attr => console.log(model.name + '.' + attr));
		}

		console.log("\nAssociations");
		console.log("\n----------------------------------\n");
		if (model._options.includeNames) {
			const names: [string] = model._options.includeNames;
			const map: [] = model._options.includeMap;

			names.forEach((name: string) => {
				console.log('\nas: ', map[name].association.as, 'type: ', map[name].association.associationType);
				console.log("----------------------------------\n");
				const accessors = map[name].association.accessors;
				for (const accessor of Object.keys(accessors)) {
					console.log(accessor, ' => ', map[name].association.accessors[accessor]);
					//console.log(model.name + '.' + model.associations[assoc].accessors[accessor] + '()');
				}
			});
		}


		if (model.Instance && model.Instance.super_) {
			console.log("\nCommon");
			for (const func of Object.keys(model.Instance.super_.prototype)) {
				if (func === 'constructor' || func === 'sequelize')
					continue;
				console.log(model.name + '.' + func + '()');
			}
		}

		console.log("\n\n----------------------------------\n",
			"END",
			"\n----------------------------------");


		return;
	}
	*/
}
