import 'package:comunic/helpers/database/database_contract.dart';
import 'package:comunic/helpers/database/database_helper.dart';
import 'package:comunic/models/cache_model.dart';

/// Model database helper
///
/// Base model for caching data in the database
///
/// @author Pierre HUBERT

abstract class ModelDatabaseHelper<T extends CacheModel> {
  /// Get the name of the target table. Must be specified by the children
  /// of this class
  String tableName();

  /// Initialize an element from a map
  T initializeFromMap(Map<String, dynamic> map);

  /// Insert an entry in the database
  Future<void> _insertDB(T el) async {
    await (await DatabaseHelper.get())!.insert(tableName(), el.toMap());
  }

  /// Update an element in the database
  Future<void> _updateDB(T el) async {
    await (await DatabaseHelper.get())!.update(
      tableName(),
      el.toMap(),
      where: "${BaseTableContract.C_ID} = ?",
      whereArgs: [el.id],
    );
  }

  /// Get an element from the database with a specified [id]
  ///
  /// Returns null if none found
  Future<T?> get(int id) async {
    List<Map> maps = await (await DatabaseHelper.get())!.query(
      tableName(),
      where: '${BaseTableContract.C_ID} = ?',
      whereArgs: [id],
    );

    if (maps.length > 0) return initializeFromMap(maps[0] as Map<String, dynamic>);

    return null;
  }

  /// Delete an entry from the database with a specified [id]
  ///
  /// Return true if at least one entry was deleted / false else
  Future<bool> delete(int id) async {
    return await (await DatabaseHelper.get())!.delete(
      tableName(),
      where: '${BaseTableContract.C_ID} = ?',
      whereArgs: [id],
    ) > 0;
  }

  /// Get all the entries from the table
  Future<List<T>> getAll() async {
    List<Map> maps = await (await DatabaseHelper.get())!.query(tableName());
    return maps.map((f) => initializeFromMap(f as Map<String, dynamic>)).toList();
  }

  /// Get some entries from the table based on some conditions
  Future<List<T>> getMultiple(
      {bool? distinct,
      List<String>? columns,
      String? where,
      List<dynamic>? whereArgs,
      String? groupBy,
      String? having,
      String? orderBy,
      int? limit,
      int? offset}) async {
    List<Map> maps = await (await DatabaseHelper.get())!.query(
      tableName(),
      distinct: distinct,
      columns: columns,
      where: where,
      whereArgs: whereArgs,
      groupBy: groupBy,
      having: having,
      orderBy: orderBy,
      limit: limit,
      offset: offset,
    );
    return maps.map((f) => initializeFromMap(f as Map<String, dynamic>)).toList();
  }

  /// Empty the table
  Future<void> clearTable() async {
    await (await DatabaseHelper.get())!.execute("DELETE FROM ${tableName()}");
  }

  /// Check out whether an element specified with its [id] is present
  /// or not in the local database
  Future<bool> has(int id) async {
    return (await get(id)) != null;
  }

  /// Insert or update information about a el (depends of the presence
  /// of previous element information in the database
  Future<void> insertOrUpdate(T el) async {
    if (await has(el.id))
      await _updateDB(el);
    else
      await _insertDB(el);
  }

  /// Insert or update an important amount of elements
  Future<void> insertOrUpdateAll(List<T> list) async {
    for (int i = 0; i < list.length; i++) await insertOrUpdate(list[i]);
  }

  /// Insert  an important amount of elements
  ///
  /// This might throw if there are conflicting IDs
  Future<void> insertAll(List<T> list) async {
    for (T el in list) await _insertDB(el);
  }
}