<?php
/**
 * Resume SQL after failed import at wp_ai_acc_flows.
 * Skips the 75 tables already imported from the first dump.
 *
 * php run-acc-export-resume.php
 */
declare(strict_types=1);

if ( PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ) {
	http_response_code( 403 );
	exit( "CLI only\n" );
}

define( 'ACC_BASE_PATH', __DIR__ );
require_once __DIR__ . '/standalone/bootstrap.php';

@set_time_limit( 0 );
@ini_set( 'memory_limit', '1024M' );

$mysqldump = 'C:\\xampp\\mysql\\bin\\mysqldump.exe';
$host = defined( 'ACC_DB_HOST' ) ? ACC_DB_HOST : 'localhost';
$user = defined( 'ACC_DB_USER' ) ? ACC_DB_USER : 'root';
$pass = defined( 'ACC_DB_PASS' ) ? ACC_DB_PASS : '';
$db   = defined( 'ACC_DB_NAME' ) ? ACC_DB_NAME : 'sademlis_ostad51';
$limit = 100;

$skipFile = __DIR__ . '/storage/backups/db/tables-before-flows-old.txt';
if ( ! is_file( $skipFile ) ) {
	fwrite( STDERR, "missing skip list — run analyze first\n" );
	exit( 1 );
}
$skip = array_fill_keys( array_filter( array_map( 'trim', file( $skipFile ) ?: [] ) ), true );
echo 'Already imported (skip): ' . count( $skip ) . PHP_EOL;

global $wpdb;
$all = $wpdb->get_col( $wpdb->prepare(
	'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s ORDER BY TABLE_NAME',
	$db
) ) ?: [];

$gis = [];
foreach ( $all as $t ) {
	if ( str_starts_with( (string) $t, $wpdb->prefix . 'gis_' ) ) {
		$gis[ (string) $t ] = true;
	}
}

$genRows = $wpdb->get_results( $wpdb->prepare(
	"SELECT TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
	 WHERE TABLE_SCHEMA = %s AND EXTRA LIKE %s",
	$db,
	'%GENERATED%'
), ARRAY_A ) ?: [];
$genMap = [];
foreach ( $genRows as $r ) {
	$genMap[ (string) $r['TABLE_NAME'] ][] = (string) $r['COLUMN_NAME'];
}

$todo = [];
foreach ( $all as $t ) {
	$t = (string) $t;
	if ( isset( $skip[ $t ] ) ) {
		continue;
	}
	$todo[] = $t;
}
echo 'Remaining tables: ' . count( $todo ) . PHP_EOL;

$outDir = __DIR__ . '/storage/backups/db';
$ts = date( 'Ymd-His' );
$name = "hosting-resume-from-flows-{$ts}.sql";
$abs = $outDir . DIRECTORY_SEPARATOR . $name;

$cnf = tempnam( sys_get_temp_dir(), 'accres' );
$passSafe = str_replace( [ '\\', '"' ], [ '\\\\', '\\"' ], $pass );
file_put_contents( $cnf, "[client]\nuser={$user}\npassword=\"{$passSafe}\"\nhost={$host}\n" );

function resume_run( array $cmd, string $out, string $label ): void {
	echo "→ {$label}\n";
	$desc = [ 1 => [ 'file', $out, 'a' ], 2 => [ 'pipe', 'w' ] ];
	$proc = proc_open( $cmd, $desc, $pipes, null, null, [ 'bypass_shell' => true ] );
	if ( ! is_resource( $proc ) ) {
		throw new RuntimeException( "fail {$label}" );
	}
	$err = stream_get_contents( $pipes[2] );
	fclose( $pipes[2] );
	$code = proc_close( $proc );
	if ( $code !== 0 ) {
		throw new RuntimeException( trim( (string) $err ) ?: "fail {$label}" );
	}
}

function resume_sql_val( $v ): string {
	if ( $v === null ) {
		return 'NULL';
	}
	if ( is_int( $v ) || is_float( $v ) ) {
		return (string) $v;
	}
	return "'" . str_replace( [ "\\", "'", "\0", "\n", "\r", "\x1a" ], [ '\\\\', "\\'", '\\0', '\\n', '\\r', '\\Z' ], (string) $v ) . "'";
}

/** @return list<string> */
function resume_writable_cols( string $db, string $table ): array {
	global $wpdb;
	$rows = $wpdb->get_results( $wpdb->prepare(
		"SELECT COLUMN_NAME, EXTRA FROM INFORMATION_SCHEMA.COLUMNS
		 WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s ORDER BY ORDINAL_POSITION",
		$db,
		$table
	), ARRAY_A ) ?: [];
	$out = [];
	foreach ( $rows as $r ) {
		if ( str_contains( strtoupper( (string) ( $r['EXTRA'] ?? '' ) ), 'GENERATED' ) ) {
			continue;
		}
		$out[] = (string) $r['COLUMN_NAME'];
	}
	return $out;
}

function resume_safe_table(
	string $mysqldump,
	string $cnf,
	string $db,
	string $table,
	string $out,
	?int $rowLimit
): void {
	global $wpdb;
	resume_run( [
		$mysqldump,
		'--defaults-extra-file=' . $cnf,
		'--single-transaction',
		'--default-character-set=utf8mb4',
		'--hex-blob',
		'--skip-triggers',
		'--no-data',
		$db,
		$table,
	], $out, "{$table} structure" );

	$cols = resume_writable_cols( $db, $table );
	if ( ! $cols ) {
		return;
	}
	$colSql = '`' . implode( '`,`', $cols ) . '`';
	$lim = $rowLimit !== null ? ( ' LIMIT ' . (int) $rowLimit ) : '';
	// phpcs:ignore
	$rows = $wpdb->get_results( "SELECT {$colSql} FROM `{$table}`{$lim}", ARRAY_A ) ?: [];
	$fh = fopen( $out, 'ab' );
	fwrite( $fh, "\nLOCK TABLES `{$table}` WRITE;\n" );
	fwrite( $fh, "/*!40000 ALTER TABLE `{$table}` DISABLE KEYS */;\n" );
	$chunk = [];
	$flush = static function () use ( &$chunk, $fh, $table, $colSql ): void {
		if ( ! $chunk ) {
			return;
		}
		fwrite( $fh, "INSERT INTO `{$table}` ({$colSql}) VALUES\n" . implode( ",\n", $chunk ) . ";\n" );
		$chunk = [];
	};
	foreach ( $rows as $row ) {
		$vals = [];
		foreach ( $cols as $c ) {
			$vals[] = resume_sql_val( $row[ $c ] ?? null );
		}
		$chunk[] = '(' . implode( ',', $vals ) . ')';
		if ( count( $chunk ) >= 150 ) {
			$flush();
		}
	}
	$flush();
	fwrite( $fh, "/*!40000 ALTER TABLE `{$table}` ENABLE KEYS */;\nUNLOCK TABLES;\n\n" );
	fclose( $fh );
	echo "→ {$table} data " . count( $rows ) . " rows\n";
}

$header = "-- ACC hosting RESUME export\n"
	. "-- Skips " . count( $skip ) . " tables already on server (before wp_ai_acc_flows)\n"
	. "-- Remaining: " . count( $todo ) . " tables | GIS ≤{$limit} | GENERATED cols safe\n"
	. '-- ' . date( 'c' ) . "\n"
	. "SET NAMES utf8mb4;\n"
	. "SET FOREIGN_KEY_CHECKS=0;\n"
	. "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';\n\n"
	. "-- Remove partial failed table if present\n"
	. "DROP TABLE IF EXISTS `wp_ai_acc_flows`;\n\n";
file_put_contents( $abs, $header );

$base = [
	$mysqldump,
	'--defaults-extra-file=' . $cnf,
	'--single-transaction',
	'--default-character-set=utf8mb4',
	'--hex-blob',
	'--skip-triggers',
];

foreach ( $todo as $table ) {
	$isGis = isset( $gis[ $table ] );
	$hasGen = isset( $genMap[ $table ] );
	if ( $hasGen || ( $isGis && $hasGen ) ) {
		resume_safe_table( $mysqldump, $cnf, $db, $table, $abs, $isGis ? $limit : null );
		continue;
	}
	if ( $isGis ) {
		resume_run( array_merge( $base, [
			'--where=1 LIMIT ' . (int) $limit,
			$db,
			$table,
		] ), $abs, "{$table} ≤{$limit}" );
		continue;
	}
	// Normal remaining table: structure+data
	resume_run( array_merge( $base, [ $db, $table ] ), $abs, $table );
}

file_put_contents( $abs, "\nSET FOREIGN_KEY_CHECKS=1;\n", FILE_APPEND );
@unlink( $cnf );

$size = (int) filesize( $abs );
$desktop = ( getenv( 'USERPROFILE' ) ?: '' ) . '\\Desktop\\' . $name;
$copied = $desktop ? @copy( $abs, $desktop ) : false;

echo json_encode( [
	'success'      => true,
	'skipped'      => count( $skip ),
	'exported'     => count( $todo ),
	'file'         => $abs,
	'desktop_copy' => $copied ? $desktop : null,
	'size_mb'      => round( $size / 1048576, 2 ),
	'note'         => 'در phpMyAdmin فقط این فایل را Import کنید. اول این SQL را هم می‌توانید جدا اجرا کنید: DROP TABLE IF EXISTS `wp_ai_acc_flows`;',
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ) . PHP_EOL;
