// ignore_for_file: depend_on_referenced_packages

import 'dart:async';
import 'dart:io';
import 'package:dart_framework/log/log.dart';
import 'package:dart_framework/utils/string_util.dart';
import 'package:dart_framework/utils/system_util.dart';
import 'package:oto/cli/cli.dart';
import 'package:oto/oto/commands/command.dart';
import 'package:oto/oto/commands/command_registry.dart';
import 'package:oto/oto/core/build_result.dart';
import 'package:oto/oto/core/defined_data.dart';
import 'package:oto/oto/core/execution_context.dart';
import 'package:oto/oto/core/system_runtime.dart';
import 'package:oto/oto/pipeline/pipeline.dart';
import 'package:oto/oto/data/command_data.dart';
import 'package:oto/oto/core/data_composer.dart';
import 'package:yaml/yaml.dart';
import 'package:path/path.dart' as path;

enum BuildType { jenkins, test, file, scheduler }

enum CommandState { ready, progress, complete }

class Application {
  Application._privateConstructor();
  static final Application _instance = Application._privateConstructor();
  static Application get instance => _instance;
  static String get current => path.current;

  String get defaultWorkspace {
    final data = commonData;
    if (data != null && data.workspace.isNotEmpty) {
      return data.workspace;
    }
    return current;
  }

  static File? _logFile;
  static bool _logEnable = true;
  static String get enter {
    return _logFile == null ? '' : '\n';
  }

  SystemRuntime systemRuntime = const DefaultSystemRuntime();

  Map<BuildType, DataComposer Function()> get _mapComposer => {
        BuildType.test: () => DataComposerTest(),
        BuildType.jenkins: () => DataComposerJenkins(runtime: systemRuntime),
        BuildType.file: () => DataComposerFile(),
      };

  ExecutionContext context = ExecutionContext();

  DataCommon? get commonData => context.commonData;
  set commonData(DataCommon? value) => context.commonData = value;

  Map<String, dynamic> get property => context.property;
  set property(Map<String, dynamic> value) => context.property = value;

  Map<String, CommandState> get commandStates => context.commandStates;
  set commandStates(Map<String, CommandState> value) =>
      context.commandStates = value;

  Map<String, DataCommand> get dataCommandMap => context.dataCommandMap;
  set dataCommandMap(Map<String, DataCommand> value) =>
      context.dataCommandMap = value;

  late Pipeline pipeline;
  BuildType _buildType = BuildType.test;

  BuildType get buildType {
    return _buildType;
  }

  Future<BuildResult> build(BuildType buildType,
      {String? yamlContent,
      DataBuild? buildData,
      bool logEnable = true,
      String? logPath}) async {
    _buildType = buildType;
    _logEnable = logEnable;
    context.resetStepEvents();
    dataCommandMap = {};
    commandStates = {};
    await setUTF8();
    registerAllCommands();

    DataBuild build;
    DataComposer? composer;
    // var envArgs = envArguments(arguments);
    // bool isTest = envArgs.containsKey('isTest'); //for Unit Test
    try {
      if (_buildType == BuildType.scheduler) {
        commonData = DataCommon.fromJson(LocalData.commonMap());
        build = buildData!;
        _logFile = File(logPath!);
        CLI.logFunc = log;
      } else {
        composer = _mapComposer[buildType]!();
        await composer.compose(yamlContent, printBuildStep);
        commonData = composer.commonData;
        final buildMap = getMapFromYamlA(composer.buildYaml);
        final mapValidate = _validateBuildMap(buildMap);
        if (!mapValidate.enable) {
          final ex = ExceptionData()
            ..phase = 'Validate build yaml'
            ..message = mapValidate.message;
          throw Exception(ex);
        }
        final commandValidate =
            _validateCommandList(buildMap!['commands'] as List);
        if (!commandValidate.enable) {
          final ex = ExceptionData()
            ..phase = 'Validate command list'
            ..message = commandValidate.message;
          throw Exception(ex);
        }
        build = DataBuild.fromJson(buildMap);
      }
      if (_logEnable) {
        await CLI.printString(
            '$enter*********************************  Build Data  *************************************',
            color: Color.magenta);
        if (composer != null) log(composer.buildYaml);
      }

      property = build.property ?? {};
      if (!property.containsKey('workspace')) {
        property['workspace'] = defaultWorkspace;
      }
      property = Command.replaceAllTagsMap(property, replace: true);

      // Register commands; filter out duplicate command IDs
      final commandMapValidate = _populateCommandMap(build, context);
      if (!commandMapValidate.enable) {
        var ex = ExceptionData();
        ex.phase = 'Validate command list';
        ex.message = commandMapValidate.message;
        throw Exception(ex);
      }

      //Parse pipeline & validate
      var validateResult = Pipeline.pipelineInitialize(build.pipeline!.workflow,
          context: context);
      if (!validateResult.enable) {
        var ex = ExceptionData();
        ex.phase = 'Validate Pipeline';
        ex.message = validateResult.message;
        throw Exception(ex);
      }
      pipeline = validateResult.pipeline!;

      //start build
      await pipeline.execute();
    } catch (e, stacktrace) {
      if (_logEnable) {
        await CLI.printString(e.toString(), color: Color.redStrong);
        await CLI.printString(stacktrace.toString(), color: Color.yellowStrong);
        await printBuildStep('Build Failed', Color.redStrong);
      }
      return BuildResult.failure(e, stacktrace, stepEvents: List<StepEvent>.of(context.stepEvents));
    }

    if (logEnable) {
      await printBuildStep('Build Successfully Complete', Color.cyan);
    }
    return BuildResult.success(stepEvents: List<StepEvent>.of(context.stepEvents));
  }

  Future setUTF8() async {
    if (systemRuntime.isWindows) {
      var data = await systemRuntime.startShell(StringBuffer('chcp 65001'),
          logHandler: logWithType);
      await data.waitForExit();
      log('Set Korean: ${data.exitCode == 0}');
    }
    return simpleFuture;
  }

  static void logWithType(String message, LogType logType) {
    log(message, logType: logType);
  }

  static void log(String message, {LogType logType = LogType.verbose}) {
    if (_logFile == null) {
      if (_logEnable) {
        print(message);
      }
    } else {
      if (_logEnable || logType == LogType.error) {
        _logFile?.writeAsStringSync('[${getDates()}] $message\n',
            mode: FileMode.append, flush: true);
      }
    }
  }

  static _ValidateResult _validateBuildMap(Map<String, dynamic>? map) {
    if (map == null) {
      return _ValidateResult(false, 'Build YAML root must be a map.');
    }
    final property = map['property'];
    if (property != null && property is! Map) {
      return _ValidateResult(
          false, '"property" must be a map, got: ${property.runtimeType}.');
    }
    if (map['commands'] == null) {
      return _ValidateResult(
          false, 'Build YAML is missing required section: "commands".');
    }
    if (map['commands'] is! List) {
      return _ValidateResult(false,
          '"commands" must be a list, got: ${map['commands'].runtimeType}.');
    }
    final pipeline = map['pipeline'];
    if (pipeline == null) {
      return _ValidateResult(
          false, 'Build YAML is missing required section: "pipeline".');
    }
    if (pipeline is! Map) {
      return _ValidateResult(
          false, '"pipeline" must be a map, got: ${pipeline.runtimeType}.');
    }
    final pipelineId = pipeline['id'];
    if (pipelineId is! String || pipelineId.trim().isEmpty) {
      return _ValidateResult(
          false, '"pipeline.id" must be a non-empty string.');
    }
    final workflow = pipeline['workflow'];
    if (workflow == null) {
      return _ValidateResult(false, '"pipeline.workflow" is missing.');
    }
    if (workflow is! List) {
      return _ValidateResult(false,
          '"pipeline.workflow" must be a list, got: ${workflow.runtimeType}.');
    }
    if (workflow.isEmpty) {
      return _ValidateResult(
          false, '"pipeline.workflow" must contain at least one task.');
    }
    return _ValidateResult(true, '');
  }

  static _ValidateResult _validateCommandList(List commandsRaw) {
    for (var i = 0; i < commandsRaw.length; i++) {
      final entry = commandsRaw[i];
      if (entry is! Map) {
        return _ValidateResult(false, 'commands[$i] must be a map.');
      }
      final id = entry['id'];
      if (id is! String || id.trim().isEmpty) {
        return _ValidateResult(
            false, 'commands[$i] "id" must be a non-empty string.');
      }
      final type = entry['command'];
      if (type is! String || type.trim().isEmpty) {
        return _ValidateResult(false,
            'commands[$i] (id: "$id") "command" must be a non-empty string.');
      }
      final typeStr = type.toString();
      final matched = CommandType.values.where((e) => e.name == typeStr);
      if (matched.isEmpty) {
        return _ValidateResult(
            false, 'commands[$i] (id: "$id") has unknown type "$typeStr".');
      }
      if (!Command.registeredTypes.contains(matched.first)) {
        return _ValidateResult(false,
            'commands[$i] (id: "$id") type "$typeStr" is not registered.');
      }
    }
    return _ValidateResult(true, '');
  }

  static dynamic _yamlToPlain(dynamic value) {
    if (value is YamlMap) {
      return {
        for (final e in value.entries) e.key.toString(): _yamlToPlain(e.value)
      };
    } else if (value is YamlList) {
      return [for (final item in value) _yamlToPlain(item)];
    }
    return value;
  }

  static Map<String, dynamic>? getMapFromYamlA(String yamlStr) {
    if (yamlStr.length <= 5) return null;
    final dynamic data = loadYaml(yamlStr);
    if (data is! YamlMap) return null;
    return _yamlToPlain(data) as Map<String, dynamic>;
  }

  void updateCommandState(String target, CommandState state) {
    commandStates[target] = state;
  }

  Future printBuildStep(String name, Color? color) async {
    if (!_logEnable) return simpleFuture;
    var message =
        '''$enter*********************************************************************************************
*                            $name
*********************************************************************************************''';
    await CLI.printString(message, color: color ?? Color.green);
    return simpleFuture;
  }

  static YamlValidationResult validateYamlContent(String yamlContent) {
    registerAllCommands();
    Map<String, dynamic>? buildMap;
    try {
      buildMap = getMapFromYamlA(yamlContent);
    } catch (e) {
      return YamlValidationResult.failure(
        phase: 'Parse YAML',
        message: e.toString(),
      );
    }

    final mapValidate = _validateBuildMap(buildMap);
    if (!mapValidate.enable) {
      return YamlValidationResult.failure(
        phase: 'Validate build yaml',
        message: mapValidate.message,
      );
    }

    final commandValidate = _validateCommandList(buildMap!['commands'] as List);
    if (!commandValidate.enable) {
      return YamlValidationResult.failure(
        phase: 'Validate command list',
        message: commandValidate.message,
      );
    }

    DataBuild build;
    try {
      build = DataBuild.fromJson(buildMap);
    } catch (e) {
      return YamlValidationResult.failure(
        phase: 'Validate build yaml',
        message: e.toString(),
      );
    }

    final validationContext = ExecutionContext();
    final commandMapValidate = _populateCommandMap(build, validationContext);
    if (!commandMapValidate.enable) {
      return YamlValidationResult.failure(
        phase: 'Validate command list',
        message: commandMapValidate.message,
      );
    }

    PipelineValidateResult pipelineValidate;
    try {
      pipelineValidate = Pipeline.pipelineInitialize(
        build.pipeline!.workflow,
        context: validationContext,
      );
    } catch (e) {
      return YamlValidationResult.failure(
        phase: 'Validate Pipeline',
        message: e.toString(),
      );
    }

    if (!pipelineValidate.enable) {
      return YamlValidationResult.failure(
        phase: 'Validate Pipeline',
        message: pipelineValidate.message ?? 'Pipeline validation failed.',
      );
    }
    return const YamlValidationResult.success();
  }

  static _ValidateResult _populateCommandMap(
      DataBuild build, ExecutionContext context) {
    for (var command in build.commands) {
      if (context.dataCommandMap.containsKey(command.id)) {
        return _ValidateResult(
          false,
          'Duplicate command id exists: "${command.id}"',
        );
      } else {
        context.dataCommandMap[command.id] = command;
      }
    }
    return const _ValidateResult(true, '');
  }
}

class _ValidateResult {
  final bool enable;
  final String message;
  const _ValidateResult(this.enable, this.message);
}

class YamlValidationResult {
  final bool valid;
  final String phase;
  final String message;
  final int exitCode;

  const YamlValidationResult.success()
      : valid = true,
        phase = 'Validate YAML',
        message = 'YAML is valid.',
        exitCode = 0;

  const YamlValidationResult.failure({
    required this.phase,
    required this.message,
    this.exitCode = 10,
  }) : valid = false;

  Map<String, dynamic> toJson() => {
        'schemaVersion': 1,
        'type': 'yamlValidation',
        'valid': valid,
        'phase': phase,
        'message': message,
        'exitCode': exitCode,
      };
}
