#!/usr/bin/env php
<?php
/**
 * ptyRun - 스크립트 중복 실행 제어 및 타임아웃 관리
 *
 * Usage: ./ptyRun "스크립트명" [옵션]
 *
 * 옵션:
 *   --run-duplicate=true|false  중복 실행 허용 여부 (기본값: true)
 *   --max-run-time=분           최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)
 *                               (--run-duplicate=false일 때만 동작)
 *   --no-log                    로그 파일 저장 비활성화 (기본값: 로그 저장 활성화)
 *   --log-dir=경로              로그 저장 폴더 (기본값: 스크립트폴더/ptyRun_logs)
 *   --log-rotate-date=일수      로그 보관 일수 (기본값: 90)
 *   --log-rotate-count=개수     로그 보관 개수 (기본값: 0 - 무제한)
 *   --help                      도움말 출력
 *
 * 예제:
 *   ./ptyRun "php /path/to/script.php" --run-duplicate=false --max-run-time=20
 *   ./ptyRun "php script.php" --no-log
 */

namespace platyFramework;

require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliOptionParser.php';
require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliLog.php';

// 인자 파싱
$parsed = ptyCliOptionParser::parse($argv);
$positionalArgs = $parsed['positional'];
$options = $parsed['options'];

// 도움말
if (empty($positionalArgs) || isset($options['help'])) {
    echo "사용법: {$argv[0]} \"스크립트명\" [옵션]\n";
    echo "\n";
    echo "옵션:\n";
    echo "  --run-duplicate=true|false  중복 실행 허용 여부 (기본값: true)\n";
    echo "  --max-run-time=분           최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)\n";
    echo "                              (--run-duplicate=false일 때만 동작)\n";
    echo "  --no-log                    로그 파일 저장 비활성화 (기본값: 로그 저장 활성화)\n";
    echo "  --log-dir=경로              로그 저장 폴더 (기본값: 스크립트폴더/ptyRun_logs)\n";
    echo "  --log-rotate-date=일수      로그 보관 일수 (기본값: 90)\n";
    echo "  --log-rotate-count=개수     로그 보관 개수 (기본값: 0 - 무제한)\n";
    echo "  --help                      도움말 출력\n";
    echo "\n";
    echo "예제:\n";
    echo "  {$argv[0]} \"php /path/to/script.php\" --run-duplicate=false --max-run-time=20\n";
    echo "  {$argv[0]} \"php script.php\" --run-duplicate=true\n";
    echo "  {$argv[0]} \"./myScript.sh\"\n";
    echo "  {$argv[0]} \"php script.php\" --no-log\n";
    echo "  {$argv[0]} \"php script.php\" --log-rotate-count=10 --log-dir=/var/log/myapp\n";
    echo "\n";
    exit(isset($options['help']) ? 0 : 1);
}

$scriptCommand = $positionalArgs[0];
$runDuplicate = !isset($options['run-duplicate']) || $options['run-duplicate'] !== 'false';
$maxRunTime = isset($options['max-run-time']) ? (int)$options['max-run-time'] : 0;
$noLog = isset($options['no-log']);
$logDir = $options['log-dir'] ?? __DIR__ . '/ptyRun_logs';
$logRotateDate = isset($options['log-rotate-date']) ? (int)$options['log-rotate-date'] : 90;
$logRotateCount = isset($options['log-rotate-count']) ? (int)$options['log-rotate-count'] : 0;

// 로그 디렉토리 생성 (로그 활성화 시에만)
if (!$noLog && !is_dir($logDir)) {
    if (!mkdir($logDir, 0755, true)) {
        echo "Error: 로그 디렉토리 생성 실패: {$logDir}\n";
        exit(1);
    }
}

// 로그 파일명 생성 (스크립트명에서 안전한 파일명 추출)
function getLogFileName($command, $logDir) {
    // 명령어에서 스크립트 파일명 추출
    preg_match('/([a-zA-Z0-9_\-\.]+\.(?:php|sh|py|rb|js|pl))/i', $command, $matches);
    $scriptName = $matches[1] ?? 'script';
    $scriptName = preg_replace('/\.[^.]+$/', '', $scriptName); // 확장자 제거
    $timestamp = date('Ymd_His');
    return $logDir . '/' . $scriptName . '_' . $timestamp . '.log';
}

// 로그 로테이션 함수
function rotateLogsByDate($logDir, $days) {
    if ($days <= 0) return;

    $cutoffTime = time() - ($days * 24 * 60 * 60);
    $files = glob($logDir . '/*.log');

    foreach ($files as $file) {
        if (filemtime($file) < $cutoffTime) {
            @unlink($file);
        }
    }
}

function rotateLogsByCount($logDir, $count) {
    if ($count <= 0) return;

    $files = glob($logDir . '/*.log');
    if (count($files) <= $count) return;

    // 파일을 수정 시간 기준 정렬 (오래된 것이 먼저)
    usort($files, function($a, $b) {
        return filemtime($a) - filemtime($b);
    });

    // 초과분 삭제
    $deleteCount = count($files) - $count;
    for ($i = 0; $i < $deleteCount; $i++) {
        @unlink($files[$i]);
    }
}

// 로그 로테이션 실행 (로그 활성화 시에만)
if (!$noLog) {
    rotateLogsByDate($logDir, $logRotateDate);
    rotateLogsByCount($logDir, $logRotateCount);
}

// 로거 인스턴스 생성
$log = ptyCliLog("RUN", ptyCliLog::COLOR_CYAN, true);

// 락 파일명 생성 (스크립트 경로를 해시로 변환)
$lockFile = '/tmp/ptyRun_' . md5($scriptCommand) . '.lock';
$pidFile = '/tmp/ptyRun_' . md5($scriptCommand) . '.pid';

try {
    // 중복 실행 체크
    if (!$runDuplicate) {
        if (file_exists($lockFile)) {
            $lockData = json_decode(file_get_contents($lockFile), true);
            $pid = $lockData['pid'] ?? 0;
            $startTime = $lockData['start_time'] ?? 0;

            // 프로세스가 실제로 실행 중인지 확인
            if ($pid && posix_kill($pid, 0)) {
                // 최대 실행 시간 체크
                if ($maxRunTime > 0 && $startTime > 0) {
                    $elapsedMinutes = (time() - $startTime) / 60;
                    if ($elapsedMinutes > $maxRunTime) {
                        $log->warning("최대 실행 시간({$maxRunTime}분) 초과. 프로세스(PID: {$pid})를 강제 종료합니다.");
                        posix_kill($pid, SIGTERM);
                        sleep(2);

                        // SIGTERM으로 종료되지 않으면 SIGKILL
                        if (posix_kill($pid, 0)) {
                            posix_kill($pid, SIGKILL);
                            sleep(1);
                        }

                        @unlink($lockFile);
                        @unlink($pidFile);
                    } else {
                        $log->info("스크립트가 이미 실행 중입니다. (PID: {$pid}, 경과: " . round($elapsedMinutes, 1) . "분)");
                        exit(0);
                    }
                } else {
                    $log->info("스크립트가 이미 실행 중입니다. (PID: {$pid})");
                    exit(0);
                }
            } else {
                // 프로세스가 없으면 락 파일 제거
                @unlink($lockFile);
                @unlink($pidFile);
            }
        }
    }

    // 로그 파일 준비 (로그 활성화 시에만)
    $logFile = null;
    $logHandle = null;
    if (!$noLog) {
        $logFile = getLogFileName($scriptCommand, $logDir);
        $logHandle = fopen($logFile, 'w');
        if (!$logHandle) {
            throw new \Exception("로그 파일 생성 실패: {$logFile}");
        }

        // 로그 헤더 작성
        $logHeader = sprintf(
            "[%s] ptyRun 시작\n명령어: %s\n%s\n",
            date('Y-m-d H:i:s'),
            $scriptCommand,
            str_repeat('-', 60)
        );
        fwrite($logHandle, $logHeader);
    }

    // 스크립트 실행
    $log->info("스크립트 실행: {$scriptCommand}");
    if ($logFile) {
        $log->info("로그 파일: {$logFile}");
    }

    // proc_open으로 실행하여 stdout/stderr 캡처
    $descriptorspec = [
        0 => ['pipe', 'r'],  // stdin
        1 => ['pipe', 'w'],  // stdout
        2 => ['pipe', 'w'],  // stderr
    ];

    $process = proc_open($scriptCommand, $descriptorspec, $pipes, null, null);

    if (!is_resource($process)) {
        if ($logHandle) fclose($logHandle);
        throw new \Exception("프로세스 실행 실패");
    }

    // 부모 프로세스의 stdin 닫기
    fclose($pipes[0]);

    // 논블로킹 모드 설정
    stream_set_blocking($pipes[1], false);
    stream_set_blocking($pipes[2], false);

    // 프로세스 정보 가져오기
    $status = proc_get_status($process);
    $pid = $status['pid'];

    if (!$runDuplicate) {
        // 락 파일 생성
        $lockData = [
            'pid' => $pid,
            'command' => $scriptCommand,
            'start_time' => time(),
            'log_file' => $logFile,
        ];
        file_put_contents($lockFile, json_encode($lockData));
        file_put_contents($pidFile, $pid);
    }

    // stdout/stderr 실시간 읽기 및 출력
    while (true) {
        $status = proc_get_status($process);

        // stdout 읽기
        while ($line = fgets($pipes[1])) {
            echo $line;
            if ($logHandle) {
                fwrite($logHandle, $line);
                fflush($logHandle);
            }
        }

        // stderr 읽기
        while ($line = fgets($pipes[2])) {
            fwrite(STDERR, $line);
            if ($logHandle) {
                fwrite($logHandle, "[STDERR] " . $line);
                fflush($logHandle);
            }
        }

        // 프로세스가 종료되었으면 남은 출력 읽고 종료
        if (!$status['running']) {
            // 남은 stdout 읽기
            while ($line = fgets($pipes[1])) {
                echo $line;
                if ($logHandle) fwrite($logHandle, $line);
            }
            // 남은 stderr 읽기
            while ($line = fgets($pipes[2])) {
                fwrite(STDERR, $line);
                if ($logHandle) fwrite($logHandle, "[STDERR] " . $line);
            }
            break;
        }

        // CPU 사용률 낮추기 위해 잠시 대기
        usleep(10000); // 10ms
    }

    fclose($pipes[1]);
    fclose($pipes[2]);

    $exitCode = proc_close($process);

    // 로그 푸터 작성 (로그 활성화 시에만)
    if ($logHandle) {
        $logFooter = sprintf(
            "\n%s\n[%s] ptyRun 종료 (Exit Code: %d)\n",
            str_repeat('-', 60),
            date('Y-m-d H:i:s'),
            $exitCode
        );
        fwrite($logHandle, $logFooter);
        fclose($logHandle);
    }

    // 락 파일 제거
    if (!$runDuplicate) {
        @unlink($lockFile);
        @unlink($pidFile);
    }

    if ($exitCode === 0) {
        $log->success("스크립트 실행 완료");
    } else {
        $log->error("스크립트 실행 실패 (Exit Code: {$exitCode})");
    }

    exit($exitCode);

} catch (\Exception $e) {
    $log->error("Error: " . $e->getMessage());
    @unlink($lockFile);
    @unlink($pidFile);
    exit(1);
}
