<?php
if (!defined('ABSPATH')) exit;

/**
 * WizMig_DBDump (v0.3.3)
 * - Fuerza UTF8MB4 en el dump (soporta emojis) y ajusta collations.
 * - Inserta cabeceras SET NAMES utf8mb4 y deshabilita FK temporalmente.
 * - Escapa valores con mysqli_real_escape_string cuando está disponible.
 */
class WizMig_DBDump {
  private $wpdb; private $from; private $to; private $dbh;
  public function __construct($wpdb, $fromDomain, $toDomain) {
    $this->wpdb = $wpdb; $this->from = rtrim($fromDomain,'/'); $this->to = rtrim($toDomain,'/');
    $this->dbh  = isset($wpdb->dbh) ? $wpdb->dbh : null;
  }

  public function export_gz() {
    $upload_dir = wp_upload_dir();
    $work = trailingslashit($upload_dir['basedir']) . 'wizhosting-migrator';
    if (!wp_mkdir_p($work)) throw new Exception('Cannot create work dir: '.$work);
    $path = trailingslashit($work) . 'wiz_db.sql.gz';
    $gz = gzopen($path, 'wb9'); if (!$gz) throw new Exception('Cannot open gz');

    // Cabecera para asegurar conexión y objetos en utf8mb4
    $header = <<<SQL
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;

SQL;
    gzwrite($gz, $header);

    $tables = $this->wpdb->get_col('SHOW TABLES');
    foreach ($tables as $table) $this->dump_table($gz, $table);

    $footer = <<<SQL
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

SQL;
    gzwrite($gz, $footer);
    gzclose($gz); return $path;
  }

  private function to_utf8mb4_create($create) {
    // Normaliza CREATE TABLE a utf8mb4
    $patterns = [
      '~CHARSET=\s*\w+~i'                    => 'CHARSET=utf8mb4',
      '~COLLATE=\s*\w+~i'                    => 'COLLATE=utf8mb4_unicode_ci',
      '~CHARACTER\s+SET\s+\w+~i'             => 'CHARACTER SET utf8mb4',
      '~COLLATE\s+utf8[a-z0-9_]*~i'          => 'COLLATE utf8mb4_unicode_ci',
      '~DEFAULT\s+CHARACTER\s+SET\s+\w+~i'   => 'DEFAULT CHARACTER SET utf8mb4',
      '~DEFAULT\s+COLLATE\s+\w+~i'           => 'DEFAULT COLLATE utf8mb4_unicode_ci',
    ];
    foreach ($patterns as $rx=>$rep) $create = preg_replace($rx, $rep, $create);
    // Si no declara charset en absoluto, lo agregamos al final
    if (!preg_match('~CHARSET=~i', $create)) {
      $create = rtrim($create, "; \n\r\t") . " DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
    }
    return $create;
  }

  private function dump_table($gz, $table) {
    $row = $this->wpdb->get_row("SHOW CREATE TABLE `$table`", ARRAY_N);
    $create = $row[1] ?? '';
    $create = $this->to_utf8mb4_create($create);
    gzwrite($gz, "DROP TABLE IF EXISTS `$table`;\n$create;\n\n");

    $count = (int)$this->wpdb->get_var("SELECT COUNT(*) FROM `$table`");
    $offset=0; $limit=500;
    $cols = $this->wpdb->get_col("SHOW COLUMNS FROM `$table`", 0);
    if (!$cols) return;
    $colList = implode('`,`', array_map(function($c){ return str_replace('`','``',$c); }, $cols));

    while ($offset < $count) {
      $rows = $this->wpdb->get_results("SELECT * FROM `$table` LIMIT $offset,$limit", ARRAY_A);
      if (!$rows) break;
      $values=[];
      foreach ($rows as $r) {
        $vals=[];
        foreach ($cols as $c) {
          $v = array_key_exists($c,$r) ? $r[$c] : null;
          if ($this->from !== $this->to && is_string($v)) $v = $this->replace_serialization_safe($v, $this->from, $this->to);
          if ($v === null) $vals[]='NULL';
          else {
            $vals[] = "'" . $this->sql_escape($v) . "'";
          }
        }
        $values[]='('.implode(',',$vals).')';
      }
      if ($values) gzwrite($gz, "INSERT INTO `$table` (`$colList`) VALUES\n".implode(",\n",$values).";\n");
      $offset += $limit;
    }
    gzwrite($gz, "\n");
  }

  private function sql_escape($v) {
    // Escapado robusto sin alterar la codificación UTF-8/emoji
    if (is_null($v)) return 'NULL';
    if (is_bool($v)) return $v ? '1' : '0';
    if (is_numeric($v) && !preg_match('/^0[0-9]+$/', (string)$v)) return (string)$v;

    if ($this->dbh && function_exists('mysqli_real_escape_string')) {
      // Asegurar conexión activa y modo UTF8MB4 si es posible (no cambia el dump, solo el escape)
      @mysqli_set_charset($this->dbh, 'utf8mb4');
      return mysqli_real_escape_string($this->dbh, (string)$v);
    }
    // Fallback
    return addslashes((string)$v);
  }

  private function replace_serialization_safe($data,$search,$replace) {
    if (!is_string($data)) return $data;
    $maybe=@unserialize($data);
    if ($maybe !== false || $data==='b:0;') { $fixed=$this->walk_replace($maybe,$search,$replace); return serialize($fixed); }
    return str_replace($search,$replace,$data);
  }
  private function walk_replace($value,$search,$replace) {
    if (is_array($value)) { foreach ($value as $k=>$v) $value[$k]=$this->walk_replace($v,$search,$replace); return $value; }
    if (is_object($value)) { foreach ($value as $k=>$v) $value->$k=$this->walk_replace($v,$search,$replace); return $value; }
    if (is_string($value)) return str_replace($search,$replace,$value);
    return $value;
  }
}

