import 'dart:io';

import 'package:cron/cron.dart';
import 'package:dart_framework/data/isolate_data.dart';
import 'package:dart_framework/platform/isolate_manager.dart';
import 'package:dart_framework/utils/string_util.dart';
import 'package:dart_framework/utils/system_util.dart';
import 'package:file_hasher/file_hasher.dart';
import 'package:oto/cli/commands/command_exe.dart';
import 'package:oto/cli/commands/scheduler/scheduler_interval.dart';
import 'package:oto/oto/application.dart';
import 'package:oto/oto/data/command_data.dart';

class IsolateScheduler extends IsolateBase {
  late String schedulerPath;
  late String logsPath;
  late String alias;
  late File file;
  late File logFile;
  int fileHash = 0;

  bool logEnable = false;
  bool notProgress = true;
  bool cronClosed = true;
  int? interval;
  String? cronExpress;
  late DataBuild build;
  SchedulerInterval? schedulerInterval;
  CommandExe commandExe = CommandExe();

  Cron? cron;
  int nextStartTime = 0;

  IsolateScheduler(Map data) {
    alias = data['alias'];
    schedulerPath = data['root'];
    logsPath = data['logs'];
    file = File('$schedulerPath/$alias.yaml');
  }

  @override
  void onReady(Object? initData) async {
    while (true) {
      await Future.delayed(Duration(seconds: 1));

      if (file.existsSync()) {
        var updated = false;
        var hash = await FileHasher.hash(file);
        if (hash != fileHash) {
          updated = true;
          fileHash = hash;
          // Reload file contents on every change
          var map = Application.getMapFromYamlA(await file.readAsString());

          //============ Validate yaml format ============//
          try {
            build = DataBuild.fromJson(map!);
          } on Exception {
            onError('Failed to parse yaml file.');
            return;
          }
          if (build.scheduler == null) {
            onError('Scheduler entry does not exist within yaml.');
            return;
          }

          var scheduler = build.scheduler!;
          if (scheduler.cron == null && scheduler.interval == null) {
            onError(
                'A scheduler script must have either a cron entry or an interval entry.');
            return;
          }

          if (scheduler.cron != null) {
            try {
              Schedule.parse(scheduler.cron!);
              cronExpress = scheduler.cron!;
            } on Error {
              onError(
                  "Cron express '${build.scheduler!.cron}' is not a suitable format.");
              return;
            }
          }

          if (scheduler.interval != null) {
            interval = scheduler.interval!;
          }

          logEnable =
              scheduler.enableLog == null ? false : scheduler.enableLog!;

          schedulerInterval?.activate = false;
        }

        //============ execute cron/interval ============//
        if (cronExpress != null) {
          //Use cron
          if (cronClosed) {
            cronClosed = false;
            cron = Cron();
            cron!.schedule(Schedule.parse(cronExpress!), () async {
              if (notProgress) {
                await execute(build);
                cronClosed = true;
                await cron!.close();
              }
            });
          }
        } else {
          //Use interval
          if (updated) {
            schedulerInterval = SchedulerInterval(interval!, build, execute);
          }
        }
      } else {
        // Exit if the file no longer exists
        onError("The file '${file.path}' does not exist.");
        if (cron != null) await cron?.close();
        return;
      }
    }
  }

  Future execute(DataBuild data) async {
    send(IsoMessege('log', data: "Scheduler '$alias' has started."));
    notProgress = false;
    logFile = File(
        '$logsPath/[$pid]_${alias}_${getDate()}_${getTime().replaceAll(':', '')}.txt');
    if (data.scheduler!.enableLog ?? false) {
      log('Start process on pid [$pid]');
    }
    await commandExe.executeScheduler(data, logEnable, logFile.path);
    notProgress = true;
    send(IsoMessege('log', data: "Scheduler '$alias' is done."));
    return simpleFuture;
  }

  void log(String message) {
    logFile.writeAsStringSync('[${getDates()}] $message\n',
        mode: FileMode.append, flush: true);
  }

  void logError(Exception e, StackTrace stacktace) {
    log('[${e.toString()}]\n${stacktace.toString()}');
  }

  void onErrorStacktrace(Exception e, StackTrace stacktrace) {
    onError('$e\n$stacktrace');
  }

  void onError(String message) {
    sendTerminated('error', message);
  }

  void sendTerminated(String type, String message) {
    send(IsoMessege('terminated',
        data: {'type': type, 'message': message, 'alias': alias}));
  }
}
