#!/usr/bin/env php
<?php
/**
 * ptyMysqlBackup
 *
 * MySQL 데이터베이스/테이블 백업 도구
 * 설정 파일: ~/.ptyMysqlConfig.ini
 *
 * Usage: ./ptyMysqlBackup <database> <table> [options]
 *        ./ptyMysqlBackup * *  (전체 백업)
 */

namespace platyFramework;

require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliOptionParser.php';
require_once __DIR__ . '/ptyLibrary_PHP/mysql/ptyMysqlConfig.php';

// 인자 파싱
$parsed = ptyCliOptionParser::parse($argv);
$positionalArgs = $parsed['positional'];
$options = $parsed['options'];
$mysqlSection = isset($options['mysql']) ? $options['mysql'] : 'default';
$verbose = isset($options['verbose']);
$outputDir = isset($options['output']) ? $options['output'] : '.';
$ignoreX = (isset($options['ignore-x']) ? $options['ignore-x'] : 'true') !== 'false';  // 기본값: true
$dryRun = isset($options['dry-run']);
$useTgz = !isset($options['no-tgz']);  // 기본값: tgz 압축 사용

// --edit 옵션: 스크립트 편집
if (isset($options['edit'])) {
    $editor = getenv('EDITOR') ?: 'vi';
    $scriptPath = __FILE__;
    passthru("$editor " . escapeshellarg($scriptPath));
    exit(0);
}

// 도움말 또는 필수 인자 확인
if (count($positionalArgs) < 2 || isset($options['help'])) {
    echo "사용법: {$argv[0]} <database> <table> [옵션]\n";
    echo "\n";
    echo "인자:\n";
    echo "  <database>          데이터베이스명 (* = 전체)\n";
    echo "  <table>             테이블명 (* = 전체)\n";
    echo "\n";
    echo "옵션:\n";
    echo "  --mysql=섹션명      INI 파일 섹션 (기본값: default)\n";
    echo "  --output=경로       출력 디렉토리 (기본값: 현재 디렉토리)\n";
    echo "  --ignore-x=true|false  x_ 접두사 DB/테이블 무시 (기본값: true)\n";
    echo "  --no-tgz            .tgz 압축 안 함 (기본값: 압축)\n";
    echo "  --dry-run           실제 백업 없이 대상 목록만 출력\n";
    echo "  --verbose           상세 로그 출력\n";
    echo "  --edit              이 스크립트를 에디터로 열기\n";
    echo "  --help              도움말 출력\n";
    echo "\n";
    echo "예시:\n";
    echo "  {$argv[0]} mydb users                    # mydb.users.sql 생성\n";
    echo "  {$argv[0]} mydb '*'                      # mydb의 모든 테이블 백업\n";
    echo "  {$argv[0]} '*' '*'                       # 전체 백업 (따옴표 필수!)\n";
    echo "  {$argv[0]} mydb users --output=/backup   # /backup/mydb.users.sql 생성\n";
    echo "  {$argv[0]} mydb users --mysql=production # production 섹션 사용\n";
    echo "  {$argv[0]} '*' '*' --ignore-x=false      # x_ 접두사 포함 전체 백업\n";
    echo "  {$argv[0]} mydb users --no-tgz           # mydb.users.sql 생성 (압축 안 함)\n";
    echo "\n";
    echo "기본 mysqldump 옵션:\n";
    echo "  --default-character-set=utf8mb4\n";
    echo "  --routines --events\n";
    echo "  --add-drop-table --add-drop-database\n";
    echo "  --complete-insert --extended-insert=TRUE\n";
    echo "  --single-transaction --ssl-mode=DISABLED\n";
    echo "  --max-allowed-packet=1G\n";
    echo "\n";
    echo "설정 파일: ~/.ptyMysqlConfig.ini\n";
    echo ptyMysqlConfig::getConfigExample() . "\n";
    exit(isset($options['help']) ? 0 : 1);
}

$dbPattern = $positionalArgs[0];
$tablePattern = $positionalArgs[1];

// 원본 명령어 저장 (백업 파일 주석용)
$originalCommand = implode(' ', $argv);

// 타임스탬프 로그 함수
function logMessage($message, $verbose = false, $isVerbose = false) {
    if ($isVerbose && !$verbose) {
        return;
    }
    $timestamp = date('Y-m-d H:i:s');
    echo "[$timestamp] $message\n";
}

// mysqldump 명령 생성
function getMysqldumpCmd($host, $user, $password, $dbName, $tableName = null) {
    $cmd = "mysqldump";
    $cmd .= " -h " . escapeshellarg($host);
    $cmd .= " -u " . escapeshellarg($user);
    $cmd .= " -p" . escapeshellarg($password);
    $cmd .= " --default-character-set=utf8mb4";
    $cmd .= " --routines --events";
    $cmd .= " --add-drop-table --add-drop-database";
    $cmd .= " --complete-insert --extended-insert=TRUE";
    $cmd .= " --single-transaction";
    $cmd .= " --ssl-mode=DISABLED";
    $cmd .= " --max-allowed-packet=1G";
    $cmd .= " " . escapeshellarg($dbName);

    if ($tableName) {
        $cmd .= " " . escapeshellarg($tableName);
    }

    return $cmd;
}

// 백업 실행 함수
function backupTable($host, $user, $password, $dbName, $tableName, $outputDir, $verbose, $originalCommand, $useTgz = false) {
    $outputFile = rtrim($outputDir, '/') . "/{$dbName}.{$tableName}.sql";
    $errorFile = sys_get_temp_dir() . "/mysqldump_err_" . getmypid() . ".txt";

    // 백업 정보 주석 생성
    $backupTime = date('Y-m-d H:i:s');
    $header = "-- ============================================\n";
    $header .= "-- 백업 시간: $backupTime\n";
    $header .= "-- 실행 명령: $originalCommand\n";
    $header .= "-- 데이터베이스: $dbName\n";
    $header .= "-- 테이블: $tableName\n";
    $header .= "-- ============================================\n\n";

    // 주석을 먼저 파일에 작성
    file_put_contents($outputFile, $header);

    $cmd = getMysqldumpCmd($host, $user, $password, $dbName, $tableName);
    // stdout은 SQL 파일에 append, stderr는 임시 파일로 분리
    $cmd .= " >> " . escapeshellarg($outputFile) . " 2>" . escapeshellarg($errorFile);

    if ($verbose) {
        logMessage("명령: $cmd", $verbose, true);
    }

    exec($cmd, $output, $returnCode);

    // 에러 확인
    $errorOutput = file_exists($errorFile) ? trim(file_get_contents($errorFile)) : '';
    @unlink($errorFile);

    if ($returnCode !== 0) {
        logMessage("ERROR: {$dbName}.{$tableName} 백업 실패", true);
        if ($errorOutput) {
            logMessage($errorOutput, true);
        }
        return false;
    }

    // 파일 크기 확인
    if (file_exists($outputFile)) {
        $size = filesize($outputFile);
        $sizeStr = formatBytes($size);

        // --tgz 옵션: 압축 후 원본 삭제
        if ($useTgz) {
            $tgzFile = rtrim($outputDir, '/') . "/{$dbName}.{$tableName}.tgz";
            $sqlBasename = "{$dbName}.{$tableName}.sql";

            // tar 명령 실행 (outputDir에서 상대 경로로 압축)
            $tarCmd = "tar -czf " . escapeshellarg($tgzFile) . " -C " . escapeshellarg(rtrim($outputDir, '/')) . " " . escapeshellarg($sqlBasename);

            if ($verbose) {
                logMessage("압축: $tarCmd", $verbose, true);
            }

            exec($tarCmd, $tarOutput, $tarReturnCode);

            if ($tarReturnCode !== 0) {
                logMessage("ERROR: {$dbName}.{$tableName} 압축 실패", true);
                return false;
            }

            // 원본 SQL 파일 삭제
            @unlink($outputFile);

            // 압축 파일 크기
            $tgzSize = filesize($tgzFile);
            $tgzSizeStr = formatBytes($tgzSize);
            $ratio = $size > 0 ? round(($tgzSize / $size) * 100, 1) : 0;
            logMessage("OK: {$dbName}.{$tableName}.tgz ($tgzSizeStr, {$ratio}% of $sizeStr)", true);
        } else {
            logMessage("OK: {$dbName}.{$tableName}.sql ($sizeStr)", true);
        }
    }

    return true;
}

// 바이트를 읽기 쉬운 형식으로 변환
function formatBytes($bytes) {
    if ($bytes >= 1073741824) {
        return number_format($bytes / 1073741824, 2) . ' GB';
    } elseif ($bytes >= 1048576) {
        return number_format($bytes / 1048576, 2) . ' MB';
    } elseif ($bytes >= 1024) {
        return number_format($bytes / 1024, 2) . ' KB';
    } else {
        return $bytes . ' bytes';
    }
}

try {
    // MySQL 연결 (database 설정 무시 - CLI 인자로 DB 지정)
    $conn = ptyMysqlConfig::connect($mysqlSection, false);
    $connection = $conn['connection'];
    $config = $conn['config'];

    logMessage($dryRun ? "MySQL 백업 (DRY-RUN)" : "MySQL 백업 시작", true);
    logMessage("Host: {$config['host']}", true);
    logMessage("User: {$config['username']}", true);
    logMessage("Output: $outputDir", true);
    if ($dryRun) {
        logMessage("Mode: DRY-RUN (실제 백업 없음)", true);
    }
    echo str_repeat("=", 60) . "\n";

    // 출력 디렉토리 생성 (dry-run 제외)
    if (!$dryRun && !is_dir($outputDir)) {
        if (!mkdir($outputDir, 0755, true)) {
            throw new \Exception("출력 디렉토리 생성 실패: $outputDir");
        }
    }

    // 시스템 DB 제외 목록
    $systemDbs = ['information_schema', 'performance_schema', 'mysql', 'sys'];

    $totalBackups = 0;
    $successBackups = 0;

    // 데이터베이스 목록 조회
    if ($dbPattern === '*') {
        $dbResult = mysqli_query($connection, "SHOW DATABASES");
        $databases = [];
        while ($row = mysqli_fetch_row($dbResult)) {
            if (!in_array($row[0], $systemDbs)) {
                $databases[] = $row[0];
            }
        }
    } else {
        $databases = [$dbPattern];
    }

    foreach ($databases as $dbName) {
        // x_ 접두사 DB 제외
        if ($ignoreX && strpos($dbName, 'x_') === 0) {
            logMessage("SKIP: $dbName (x_ prefix)", true);
            continue;
        }

        logMessage("", true);
        logMessage("Database: $dbName", true);
        echo str_repeat("-", 40) . "\n";

        // 테이블 목록 조회
        if (strpos($tablePattern, '*') !== false) {
            // 와일드카드 패턴이 있으면 전체 테이블 조회 후 필터링
            $tableResult = mysqli_query($connection, "SHOW TABLES FROM `$dbName`");
            if (!$tableResult) {
                logMessage("ERROR: $dbName 테이블 조회 실패", true);
                continue;
            }

            $tables = array();
            while ($row = mysqli_fetch_row($tableResult)) {
                $tableName = $row[0];
                // fnmatch로 패턴 매칭 (*, ? 지원)
                if (fnmatch($tablePattern, $tableName)) {
                    $tables[] = $tableName;
                }
            }

            if (empty($tables)) {
                logMessage("SKIP: $dbName - 패턴 '$tablePattern'에 맞는 테이블 없음", true);
                continue;
            }
        } else {
            $tables = array($tablePattern);
        }

        foreach ($tables as $tableName) {
            // x_ 접두사 테이블 제외
            if ($ignoreX && strpos($tableName, 'x_') === 0) {
                logMessage("SKIP: $dbName.$tableName (x_ prefix)", true);
                continue;
            }
            $totalBackups++;
            if ($dryRun) {
                $ext = $useTgz ? 'tgz' : 'sql';
                logMessage("WOULD BACKUP: $dbName.$tableName -> $outputDir/$dbName.$tableName.$ext", true);
                $successBackups++;
            } else {
                if (backupTable($config['host'], $config['username'], $config['password'], $dbName, $tableName, $outputDir, $verbose, $originalCommand, $useTgz)) {
                    $successBackups++;
                }
            }
        }
    }

    mysqli_close($connection);

    echo "\n" . str_repeat("=", 60) . "\n";
    if ($dryRun) {
        logMessage("DRY-RUN 완료: $successBackups / $totalBackups 대상", true);
    } else {
        logMessage("백업 완료: $successBackups / $totalBackups", true);
    }

    if ($successBackups < $totalBackups) {
        exit(1);
    }

} catch (\Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
    exit(1);
}
