import 'dart:io';

import 'package:dartx/dartx_io.dart';
import 'package:guru_utils/file/file_utils.dart';
import 'package:guru_utils/id/id_utils.dart';
import 'package:guru_utils/log/log.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
export 'batch/batch_data.dart';
export 'batch/batch_aware.dart';
export 'package:sqflite/sqflite.dart';

/// Created by Haoyi on 2022/8/24
///
part 'migration.dart';

typedef TableCreator = Future Function(Transaction delegate);

abstract class AppDatabase {
  late Database _database;

  List<TableCreator> get tableCreators;

  List<Migration> get migrations;

  int get version;

  String get dbName;

  Future<String> getTestingPath() async {
    return "./mocks_dir/db";
  }

  Future _createTables(Database db) {
    return db.transaction((Transaction delegate) async {
      final List<TableCreator> creators = tableCreators;
      for (var creator in creators) {
        await creator(delegate);
      }
    });
  }

  Future _migrate(Database database, int from, int to) async {
    Log.d("MIGRATE [$from] => [$to]");
    final _migrations = migrations;
    return database.transaction((txn) async {
      for (int index = from - 1; (index < to - 1) && (index < _migrations.length); ++index) {
        var result = MigrateResult.failed;
        try {
          Log.d("====> BEGIN MIGRATE [${index + 1}] => [${index + 2}]");
          result = await _migrations[index].migrate(txn);
        } catch (error) {
          Log.d("  migrate [${index + 1}] => [${index + 2}] error:[$error]");
          rethrow;
        }
        Log.d("====> END MIGRATE [${index + 1}] => [${index + 2}] result: $result");
      }
    });
  }
  Future initDatabase() async {
    final isTesting = Platform.environment.containsKey('FLUTTER_TEST');
    final dbDir = isTesting ? await getTestingPath() : await getDatabasesPath();
    final dbPath = join(dbDir, "$dbName.db");
    Log.d("dbPath:$dbPath");
    _database = await openDatabase(
      dbPath,
      version: version,
      onCreate: (Database db, int version) async {
        // When creating the db, create the table
        try {
          await _createTables(db);
        } catch (error, stacktrace) {
          Log.w("createTables error! $error", stackTrace: stacktrace);
        }
      },
      onUpgrade: (Database db, int oldVersion, int newVersion) async {
        await _migrate(db, oldVersion, newVersion);
      },
    );
  }

  Database getDb() {
    return _database;
  }

  Future<T> runInTransaction<T>(Future<T> Function(Transaction txn) action) {
    return getDb().transaction(action);
  }

  Future backup(String token) async {
    final uuid = IdUtils.keyUuid(token);
    final isTesting = Platform.environment.containsKey('FLUTTER_TEST');
    final dbDir = isTesting ? await getTestingPath() : await getDatabasesPath();
    final dbFile = File(join(dbDir, "$dbName.db"));
    final backupDbFile = File(join(dbDir, "$dbName.$uuid.db"));
    Log.d("backup dbPath:${dbFile.name} to ${backupDbFile.name}");
    if (await FileUtils.instance.checkFileExists(backupDbFile)) {
      await backupDbFile.delete();
    }
    await dbFile.copy(backupDbFile.path);
  }

  Future<bool> restore(String token) async {
    final uuid = IdUtils.keyUuid(token);
    final isTesting = Platform.environment.containsKey('FLUTTER_TEST');
    final dbDir = isTesting ? await getTestingPath() : await getDatabasesPath();
    final dbFile = File(join(dbDir, "$dbName.db"));
    final restoreDbFile = File(join(dbDir, "$dbName.$uuid.db"));
    Log.d("restore dbPath:${restoreDbFile.name} to ${dbFile.name}");
    if (await FileUtils.instance.checkFileExists(dbFile)) {
      _database.close();
      await dbFile.delete();
    }

    if (await FileUtils.instance.checkFileExists(restoreDbFile) == false) {
      Log.w("backupDbFile not exists");
      return false;
    }
    await restoreDbFile.copy(dbFile.path);
    return true;
  }

  Future<bool> switchSession(String oldToken, String newToken) async {
    if (oldToken == newToken) {
      Log.w("same uid, no need to switch");
      return false;
    }
    await getDb().close();
    if (oldToken.isNotEmpty) {
      await backup(oldToken);
    }
    await restore(newToken);
    await initDatabase();
    return true;
  }
}

extension DatabaseExt on AppDatabase {
  String joinTextValue(List<String> value, [String separator = ""]) {
    return value.map((str) => "'$str'").toList().join(separator);
  }
}
