import 'package:dart_framework/utils/system_util.dart';
import 'package:oto/oto/application.dart';
import 'package:oto/oto/core/execution_context.dart';
import 'package:oto/oto/pipeline/pipeline_contain.dart';
import 'package:oto/oto/pipeline/pipeline_exe.dart';
import 'package:oto/oto/pipeline/pipeline_exe_handle.dart';
import 'package:oto/oto/pipeline/pipeline_foreach.dart';
import 'package:oto/oto/pipeline/pipeline_if.dart';
import 'package:oto/oto/pipeline/pipeline_switch.dart';
import 'package:oto/oto/pipeline/pipeline_wait_until.dart';
import 'package:oto/oto/pipeline/pipeline_while.dart';

class Pipeline {
  static Map<String, PipelineExecutor Function()> exeMap = {
    'exe': () => PipelineExe(),
    'exe-handle': () => PipelineExeHandle(),
    'async': () => PipelineAsync(),
    'if': () => PipelineIf(),
    'contain': () => PipelineContain(),
    'while': () => PipelineWhile(),
    'foreach': () => PipelineForeach(),
    'wait-until-int': () => PipelineWaitUntil('int'),
    'wait-until-float': () => PipelineWaitUntil('float'),
    'wait-until-double': () => PipelineWaitUntil('double'),
    'wait-until-string': () => PipelineWaitUntil('string'),
    'wait-until-seconds': () => PipelineWaitUntilSeconds(),
    'switch': () => PipelineSwitch(),
  };

  //pipeline validate & initialize
  static PipelineValidateResult pipelineInitialize(List<dynamic> list,
      {ExecutionContext? context}) {
    final runtimeContext = context ?? Application.instance.context;
    var result = PipelineValidateResult()..context = runtimeContext;
    for (var index = 0; index < list.length; index++) {
      final raw = list[index];
      if (raw is! Map || raw.length != 1) {
        result
          ..enable = false
          ..message =
              'workflow[$index] must be a single-key task map. Supported tasks: ${exeMap.keys.join(', ')}.';
        return result;
      }
      final item = Map<String, dynamic>.from(raw);
      final key = item.keys.first;
      if (!exeMap.containsKey(key)) {
        result
          ..enable = false
          ..message =
              'workflow[$index]: "$key" is not a supported task. Supported tasks: ${exeMap.keys.join(', ')}.';
        return result;
      }

      var exe = exeMap[key]!();
      exe.workflowKey = key;
      exe.context = runtimeContext;
      var childResult = exe.initialize(item);
      if (!childResult.enable) {
        childResult.message =
            'workflow[$index] ($key): ${childResult.message ?? 'initialization failed'}';
        return childResult;
      }
      result.exeList.add(exe);
    }
    return result;
  }

  final ExecutionContext context;
  final List<PipelineExecutor> _exeList;

  Pipeline(this._exeList, this.context);

  Future execute() async {
    for (var index = 0; index < _exeList.length; index++) {
      final exe = _exeList[index];
      final stepId = context.allocateStepEventId();
      context.addStepEvent(StepEvent(
        stepId: stepId,
        workflowIndex: index,
        stepType: exe.stepType,
        event: 'started',
        timestamp: DateTime.now().toUtc().toIso8601String(),
        commandId: exe.stepCommandId,
        commandType: exe.stepCommandType,
      ));
      try {
        await exe.execute();
        context.addStepEvent(StepEvent(
          stepId: stepId,
          workflowIndex: index,
          stepType: exe.stepType,
          event: 'completed',
          timestamp: DateTime.now().toUtc().toIso8601String(),
          commandId: exe.stepCommandId,
          commandType: exe.stepCommandType,
        ));
      } catch (e) {
        context.addStepEvent(StepEvent(
          stepId: stepId,
          workflowIndex: index,
          stepType: exe.stepType,
          event: 'failed',
          timestamp: DateTime.now().toUtc().toIso8601String(),
          commandId: exe.stepCommandId,
          commandType: exe.stepCommandType,
          error: {'message': e.toString()},
        ));
        rethrow;
      }
    }
    return simpleFuture;
  }
}

class PipelineValidateResult {
  String? message;
  late bool enable = true;
  late List<PipelineExecutor> exeList = [];
  ExecutionContext? context;

  Pipeline? get pipeline {
    if (exeList.isNotEmpty) {
      return Pipeline(exeList, context ?? Application.instance.context);
    } else {
      return null;
    }
  }
}
