import 'dart:io';
import 'dart:async';

import 'package:dart_framework/utils/path.dart';
import 'package:dart_framework/utils/system_util.dart';
import 'package:dart_framework/platform/process.dart';
import 'package:path/path.dart' as path;

class OSStartup {
  static _Startup? _instance;
  static _Startup? get _current {
    if (_instance == null) {
      if (Platform.isMacOS) {
        _instance = _StartupMac();
      } else if (Platform.isLinux) {
        _instance = _StartupLinux();
      } else {
        _instance = _StartupWindows();
      }
    }
    return _instance;
  }

  static Future<bool> isRegistedStartup(String label) async {
    return _current!.isRegistedStartup(label);
  }

  //shell file must have '#!/bin/sh'
  static Future<bool> registStartup(String executableFilePath, String label,
      {List<String>? arguments,
      String? logStandardFilePath,
      String? logErrorFilePath}) async {
    return _current!.registStartup(executableFilePath, label,
        arguments: arguments,
        logStandardFilePath: logStandardFilePath,
        logErrorFilePath: logErrorFilePath);
  }

  static Future<bool> unregistStartup(String label) async {
    return _current!.unregistStartup(label);
  }
}

class _Startup {
  Future<bool> isRegistedStartup(String label) async {
    var c = Completer<bool>();
    return c.future;
  }

  //shell file must have '#!/bin/sh'
  Future<bool> registStartup(String executableFilePath, String label,
      {List<String>? arguments,
      String? logStandardFilePath,
      String? logErrorFilePath}) async {
    var c = Completer<bool>();
    return c.future;
  }

  Future<bool> unregistStartup(String label) async {
    var c = Completer<bool>();
    return c.future;
  }
}

/// ************* MAC ***************
class _StartupMac extends _Startup {
  final plistTemplate = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>KeepAlive</key>
        <false/>
        <key>Label</key>
        <string>{LABEL}</string>
        <key>ProgramArguments</key>
        <array>
                <string>{FILE_PATH}</string>                
        </array>
        <key>RunAtLoad</key>
        <true/>{LOG_STANDARD}{LOG_ERROR}
    </dict>
</plist>""";

  final pathTemplate = """#!/bin/bash

# Load .zprofile if it exists
if [ -f ~/.zprofile ]; then
    source ~/.zprofile
fi

# Load .bash_profile if it exists
if [ -f ~/.bash_profile ]; then
    source ~/.bash_profile
fi

# Load .bashrc if it exists (optional)
if [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi

# Load .zshrc if it exists (optional)
if [ -f ~/.zshrc ]; then
    source ~/.zshrc
fi

# Run your command
{COMMAND}
""";

  final logStandardTemplate = """

        <key>StandardOutPath</key>
        <string>{LOG_STANDARD}</string>""";
  final logErrorTemplate = """

        <key>StandardErrorPath</key>
        <string>{LOG_ERROR}</string>""";

  @override
  Future<bool> isRegistedStartup(String label) async {
    var c = Completer<bool>();
    var plistName = '$label.plist';
    var userPath = await Path.userPath;
    c.complete(
        await File('$userPath/Library/LaunchAgents/$plistName').exists());
    return c.future;
  }

  //shell file must have '#!/bin/sh'
  @override
  Future<bool> registStartup(String executableFilePath, String label,
      {List<String>? arguments,
      String? logStandardFilePath,
      String? logErrorFilePath}) async {
    final tagLabel = '{LABEL}';
    final tagPath = '{FILE_PATH}';
    final plistName = '$label.plist';
    final userPath = await Path.userPath;
    var success = false;
    final appDir = Directory('$userPath/Library/$label');
    final file = File('${appDir.path}/launch.sh');

    if (!appDir.existsSync()) {
      appDir.createSync(recursive: true);
    }

    var param = arguments == null ? '' : ' ${arguments.join(' ')}';
    var launchContent =
        pathTemplate.replaceAll('{COMMAND}', '$executableFilePath$param');
    file.writeAsStringSync(launchContent);

    //create plist in LaunchAgents

    final tagLogStandard = '{LOG_STANDARD}';
    final tagLogError = '{LOG_ERROR}';
    var plistContent = plistTemplate
        .replaceAll(tagLabel, label)
        .replaceAll(tagPath, file.path);
    if (logStandardFilePath == null) {
      plistContent = plistContent.replaceAll(tagLogStandard, '');
    } else {
      plistContent = plistContent.replaceAll(tagLogStandard,
          logStandardTemplate.replaceAll(tagLogStandard, logStandardFilePath));
    }
    if (logErrorFilePath == null) {
      plistContent = plistContent.replaceAll(tagLogError, '');
    } else {
      plistContent = plistContent.replaceAll(tagLogError,
          logErrorTemplate.replaceAll(tagLogError, logErrorFilePath));
    }
    var plistFile = File('$userPath/Library/LaunchAgents/$plistName');
    await plistFile.create();
    await plistFile.writeAsString(plistContent);

    var result = await ProcessExecutor.run(StringBuffer(
        'chmod +x "$executableFilePath" && chmod +x "${file.path}" '));
    success = result.exitCode == 0;

    return dataFutrue(success);
  }

  @override
  Future<bool> unregistStartup(String label) async {
    var c = Completer<bool>();
    var success = false;
    var plistName = '$label.plist';
    var userPath = await Path.userPath;
    var file = File('$userPath/Library/LaunchAgents/$plistName');
    if (file.existsSync()) {
      await file.delete();
      success = true;
    }
    c.complete(success);
    return c.future;
  }
}

/// ************* LINUX ***************
class _StartupLinux extends _Startup {
  final serviceTemplate = """[Unit]
Description={LABEL}
#Documentation=http://toki-labs.com

[Service]
Type=simple
#User=deck
#Group=deck
#TimeoutStartSec=0
#Restart=on-failure
#RestartSec=30s
#ExecStartPre=
ExecStart={FILE_PATH}
#SyslogIdentifier=TestService
#ExecStop=

[Install]
WantedBy=default.target""";

  @override
  Future<bool> isRegistedStartup(String label) async {
    var c = Completer<bool>();
    var exist = false;
    var userPath = await Path.userPath;
    exist =
        await File('$userPath/.config/systemd/user/$label.service').exists();
    c.complete(exist);
    return c.future;
  }

  //shell file must have '#!/bin/sh'
  @override
  Future<bool> registStartup(String executableFilePath, String label,
      {List<String>? arguments,
      String? logStandardFilePath,
      String? logErrorFilePath}) async {
    var c = Completer<bool>();
    final tagLabel = '{LABEL}';
    final tagPath = '{FILE_PATH}';
    ProcessResult? result;

    var userPath = await Path.userPath;
    var systemPath = '$userPath/.config/systemd/user';
    if (arguments != null) {
      for (var arg in arguments) {
        executableFilePath += ' $arg';
      }
    }
    var serviceContent = serviceTemplate
        .replaceAll(tagLabel, label)
        .replaceAll(tagPath, executableFilePath);

    var serviceFile = File('$systemPath/$label.service');
    await serviceFile.create();
    await serviceFile.writeAsString(serviceContent);

    var shell = StringBuffer();
    shell.write('cd $systemPath && ');
    shell.write('systemctl --user enable $label && ');
    shell.write('systemctl --user daemon-reload');
    result = await ProcessExecutor.run(shell);
    print('exitCode: ${result.exitCode}');
    c.complete(result.exitCode == 0);
    return c.future;
  }

  @override
  Future<bool> unregistStartup(String label) async {
    var c = Completer<bool>();
    var success = false;
    var userPath = await Path.userPath;
    var shell = StringBuffer();
    var systemPath = '$userPath/.config/systemd/user';
    shell.write('cd $systemPath && ');
    shell.write('systemctl --user stop $label && ');
    shell.write('systemctl --user disable $label && ');
    shell.write('systemctl --user daemon-reload');
    var result = await ProcessExecutor.run(shell);
    success = result.exitCode == 0;

    if (success) {
      success = false;
      var file = File('$systemPath/$label.service');
      if (await file.exists()) {
        await file.delete();
        success = true;
      }
    }
    c.complete(success);
    return c.future;
  }
}

/// ************* WINDOWS ***************
class _StartupWindows extends _Startup {
  final startupScriptTemplate = '''Start-Process -FilePath "{EXECUTABLE_PATH}" `
              {ARGUMENTS}-WindowStyle Hidden{LOG_STDOUT}{LOG_STDERR}''';

  final schedulerScriptTemplate = '''
# 실행할 PS1 스크립트 경로
\$ScriptPath = "{SCRIPT_PATH}"

# 작업 이름 설정
\$TaskName = "{TASK_NAME}"

# 트리거 정의 (사용자 로그온 시 실행)
\$Trigger = New-ScheduledTaskTrigger -AtLogOn
\$Trigger

# 작업 동작 정의
\$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File `"\$ScriptPath`""
\$Action

# 작업 등록
Register-ScheduledTask -TaskName \$TaskName -Action \$Action -Trigger \$Trigger

Exit
''';

  String getScriptPath(String label) {
    return path.join(dataPath.replaceAll('/', '\\'), 'startup', '$label.ps1');
  }

  @override
  Future<bool> isRegistedStartup(String label) async {
    await ProcessExecutor.initPowershell();
    var shell = 'Get-ScheduledTask | Where-Object {\$_.TaskName -eq "$label"}';
    var process = await Process.run('powershell', [shell]);
    var exist = false;
    if (process.exitCode == 0) {
      var existLine = <String>[];
      var stdoutList = process.stdout.toString().split('\n');
      for (var item in stdoutList) {
        var result = item.trim();
        if (result.isNotEmpty) {
          existLine.add(item);
        }
      }
      if (existLine.length > 2) {
        exist = true;
      }
    }
    return dataFutrue(exist);
  }

  //shell file must have '#!/bin/sh'
  @override
  Future<bool> registStartup(String executableFilePath, String label,
      {List<String>? arguments,
      String? logStandardFilePath,
      String? logErrorFilePath}) async {
    var scriptPath = getScriptPath(label);
    var startupScript = startupScriptTemplate.replaceAll(
        '{EXECUTABLE_PATH}', executableFilePath.replaceAll('/', '\\'));
    var args = '';
    if (arguments != null) {
      args = '''-ArgumentList "${arguments.join(' ')}" `
              ''';
    }
    startupScript = startupScript.replaceAll('{ARGUMENTS}', args);
    var logStdout = '';
    if (logStandardFilePath != null) {
      logStdout = ''' `
              -RedirectStandardOutput "$logStandardFilePath"''';
    }
    startupScript = startupScript.replaceAll('{LOG_STDOUT}', logStdout);

    var logStderr = '';
    if (logErrorFilePath != null) {
      logStderr = ''' `
              -RedirectStandardError "$logErrorFilePath"''';
    }
    startupScript = startupScript.replaceAll('{LOG_STDERR}', logStderr);

    var startupFile = File(scriptPath);
    startupFile.createSync(recursive: true);
    startupFile.writeAsStringSync(startupScript, flush: true);

    var schedulerScript = schedulerScriptTemplate
        .replaceAll('{SCRIPT_PATH}', scriptPath)
        .replaceAll('{TASK_NAME}', label);

    var tempFile =
        File('${Directory.systemTemp.path}\\${ProcessExecutor.tempFileName}');
    tempFile.createSync(recursive: true);
    tempFile.writeAsStringSync(schedulerScript, flush: true);

    var powershellCommand =
        'Start-Process powershell.exe -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command `& \'${tempFile.path}\'; exit" -Verb RunAs -WindowStyle Hidden';

    await ProcessExecutor.initPowershell();
    var process = await Process.run(
        'powershell', ['-Command', powershellCommand, '-Wait']);

    print(process.stdout.toString());
    print(process.stderr.toString());
    print(process.exitCode);

    tempFile.deleteSync();

    return dataFutrue(process.exitCode == 0);
  }

  @override
  Future<bool> unregistStartup(String label) async {
    await ProcessExecutor.initPowershell();
    var shell = 'Unregister-ScheduledTask -TaskName "$label" -Confirm:\$false';
    var process = await Process.run(
      'powershell',
      [
        '-Command',
        'Start-Process powershell -ArgumentList \'-NoProfile -ExecutionPolicy Bypass -Command "$shell"\' -Verb RunAs -WindowStyle Hidden'
      ],
      runInShell: true,
    );
    print(process.stdout.toString());
    print(process.stderr.toString());
    print(process.exitCode);

    var scriptFile = File(getScriptPath(label));
    if (scriptFile.existsSync()) {
      scriptFile.deleteSync();
    }

    return dataFutrue(process.exitCode == 0);
  }
}
