<?php
namespace Core;

/**
 * 安装 / 数据升级 核心逻辑（供 install.php、后台「数据升级」与「数据库初始化」共用）
 *
 * 设计原则：
 *  - install()  首次安装：建全部表 + 插种子（可指定自定义超级管理员），不破坏已有数据。
 *  - upgrade()  增量升级：补齐新增模块表/列 + 按 id 补齐缺失种子，绝不 DELETE / 覆盖客户数据。
 *  - 所有 DDL 均为 IF NOT EXISTS / ADD COLUMN IF NOT EXISTS 幂等写法。
 */
class Installer
{
    /** 完整安装（首次）。$super 非空时用自定义超级管理员覆盖种子账号。 */
    public static function install(?array $super = null): array
    {
        $seed = require BASE_PATH . '/install/seed.php';
        if ($super) {
            $seed['admin_users'] = [[
                'id'         => 1,
                'username'   => $super['username'],
                'password'   => password_hash($super['password'], PASSWORD_DEFAULT),
                'name'       => $super['name'] ?? '超级管理员',
                'role'       => 'super_admin',
                'crm_role'   => 'admin',
                'psi_role'   => 'admin',
                'status'     => 1,
                'created_at' => date('Y-m-d'),
            ]];
        }
        $driver = Db::driver();
        $msgs = [];
        if ($driver === 'file') {
            $dir = Db::fileDir();
            foreach ($seed as $table => $rows) {
                $i = 1;
                foreach ($rows as &$r) { if (!isset($r['id'])) { $r['id'] = $i; } $i++; }
                unset($r);
                file_put_contents($dir . "/{$table}.json", json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
                $msgs[] = "写入 {$table}.json (" . count($rows) . " 条)";
            }
            foreach (['crm_contacts'] as $t) {
                $f = $dir . "/{$t}.json";
                if (!is_file($f)) { file_put_contents($f, '[]'); $msgs[] = "创建 {$t}.json"; }
            }
        } else {
            $pdo = Db::pdo();
            $pdo->exec(file_get_contents(BASE_PATH . '/install/schema.sql'));
            $msgs[] = "数据表已创建/更新（基础 + CRM + PSI）";
            foreach (['categories', 'products', 'news', 'cases'] as $t) {
                try { $pdo->exec("ALTER TABLE `{$t}` ADD COLUMN `layout` TEXT"); } catch (\Throwable $e) {}
            }
            self::ensureColumns($pdo, $msgs);
            $map = self::modelMap();
            foreach ($seed as $table => $rows) {
                $m = $map[$table] ?? null;
                if (!$m) continue;
                foreach ($rows as $r) { $m->insert($r); }
                $msgs[] = "插入 {$table} (" . count($rows) . " 条)";
            }
        }
        try { Theme::regenerate(); $msgs[] = "主题样式 theme.css 已生成"; } catch (\Throwable $e) {}
        @file_put_contents(BASE_PATH . '/storage/installed.lock', date('Y-m-d H:i:s') . " installed\n");
        return $msgs;
    }

    /** 数据升级（后台按钮 / 已安装系统）：补齐新模块表/列，按 id 补齐缺失种子，保留客户数据 */
    public static function upgrade(): array
    {
        $seed = require BASE_PATH . '/install/seed.php';
        $driver = Db::driver();
        $msgs = [];
        $content = [
            'categories', 'products', 'news', 'cases', 'pages', 'banners', 'settings',
            'crm_customers', 'crm_leads', 'crm_followups', 'crm_contacts',
            'psi_materials', 'psi_products', 'psi_suppliers', 'psi_purchases', 'psi_sales',
        ];
        if ($driver === 'file') {
            $dir = Db::fileDir();
            foreach ($seed as $table => $rows) {
                if (!in_array($table, $content, true)) continue;
                $ef = $dir . "/{$table}.json";
                $existing = [];
                if (is_file($ef)) {
                    $ed = @json_decode(file_get_contents($ef), true);
                    if (is_array($ed)) $existing = $ed;
                }
                if ($table === 'settings') {
                    $keys = [];
                    foreach ($existing as $er) { if (isset($er['skey'])) $keys[$er['skey']] = true; }
                    foreach ($rows as $r) { if (!isset($keys[$r['skey']])) $existing[] = $r; }
                } else {
                    $ids = [];
                    foreach ($existing as $er) { if (isset($er['id'])) $ids[$er['id']] = true; }
                    foreach ($rows as $r) {
                        $rid = $r['id'] ?? null;
                        if ($rid !== null && !isset($ids[$rid])) $existing[] = $r;
                    }
                }
                file_put_contents($ef, json_encode($existing, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
                $msgs[] = "刷新 {$table}.json (" . count($existing) . " 条，保留客户数据)";
            }
            foreach (['orders', 'payments', 'crm_contacts'] as $t) {
                $f = $dir . "/{$t}.json";
                if (!is_file($f)) { file_put_contents($f, '[]'); $msgs[] = "创建 {$t}.json"; }
            }
        } else {
            $pdo = Db::pdo();
            $pdo->exec(file_get_contents(BASE_PATH . '/install/schema.sql'));
            $msgs[] = "数据表已创建/更新（补齐新增模块表）";
            foreach (['categories', 'products', 'news', 'cases'] as $t) {
                try { $pdo->exec("ALTER TABLE `{$t}` ADD COLUMN `layout` TEXT"); } catch (\Throwable $e) {}
            }
            self::ensureColumns($pdo, $msgs);
            $map = self::modelMap();
            foreach ($seed as $table => $rows) {
                if (!in_array($table, $content, true)) continue;
                $m = $map[$table] ?? null;
                if (!$m) continue;
                if ($table === 'settings') {
                    $keys = [];
                    try { $rs = Db::query("SELECT skey FROM `settings`"); foreach ($rs->fetchAll() as $er) $keys[$er['skey']] = true; } catch (\Throwable $e) {}
                    $added = 0;
                    foreach ($rows as $r) { if (!isset($keys[$r['skey']])) { $m->insert($r); $added++; } }
                    $msgs[] = "补齐 settings (" . $added . " 条)";
                    continue;
                }
                $ids = [];
                try { $rs = Db::query("SELECT id FROM `{$table}`"); foreach ($rs->fetchAll() as $er) $ids[$er['id']] = true; } catch (\Throwable $e) {}
                $added = 0;
                foreach ($rows as $r) {
                    $rid = $r['id'] ?? null;
                    if ($rid !== null && !isset($ids[$rid])) { $m->insert($r); $added++; }
                }
                $msgs[] = "补齐 {$table} 缺失种子 (" . $added . " 条，已有 " . count($ids) . " 条保留)";
            }
        }
        try { Theme::regenerate(); $msgs[] = "主题样式 theme.css 已生成"; } catch (\Throwable $e) {}
        return $msgs;
    }

    /** 执行单个 SQL 文件（用于「数据库升级」的升级包）。失败会抛出异常由调用方捕获。 */
    public static function applySqlFile(string $path): void
    {
        if (!is_file($path)) {
            throw new \RuntimeException("升级文件不存在：{$path}");
        }
        $sql = file_get_contents($path);
        if ($sql === false || trim($sql) === '') return;
        self::dbExecute($sql);
    }

    /**
     * 分段执行 SQL（支持 DELIMITER 命令，兼容 PDO 不支持的客户端语法）。
     * @param string $sql 原始 SQL 文本（含 / 不含 DELIMITER 均可）
     * @throws \Throwable
     */
    private static function dbExecute(string $sql): void
    {
        $pdo = Db::pdo();
        // 逐行解析 DELIMITER 与多语句拆分
        $lines = explode("\n", $sql);
        $delimiter = ';';
        $buffer = '';
        foreach ($lines as $raw) {
            $line = trim($raw);
            // 跳过空行与单行注释（兼容 PHP 7，不用 str_starts_with）
            if ($line === '' || strpos($line, '--') === 0 || strpos($line, '#') === 0) continue;
            // 检测 DELIMITER 命令（客户端命令，不进入 SQL 执行）
            if (strtoupper(substr($line, 0, 10)) === 'DELIMITER ') {
                // 积压的 SQL 遇到 DELIMITER 修改时先执行
                $stmt = trim($buffer);
                if ($stmt !== '') {
                    if ($pdo->exec($stmt) === false) {
                        $err = $pdo->errorInfo();
                        throw new \RuntimeException("SQL 执行错误：{$err[2]} (SQL: " . substr($stmt, 0, 120) . ')');
                    }
                }
                $buffer = '';
                $delimiter = trim(substr($line, 10));
                continue;
            }
            $buffer .= $raw . "\n";
            // 检查 buffer 是否以当前分隔符结尾（忽略末尾空白与行末注释）
            $trimmed = rtrim($buffer, " \t\r\n");
            if (substr($trimmed, -strlen($delimiter)) === $delimiter) {
                $stmt = rtrim(substr($trimmed, 0, -strlen($delimiter)));
                $stmt = trim($stmt);
                if ($stmt !== '') {
                    if ($pdo->exec($stmt) === false) {
                        $err = $pdo->errorInfo();
                        throw new \RuntimeException("SQL 执行错误：{$err[2]} (SQL: " . substr($stmt, 0, 120) . ')');
                    }
                }
                $buffer = '';
            }
        }
        // 最后一段（无结束分隔符的纯 SQL）
        $stmt = trim($buffer);
        if ($stmt !== '') {
            if ($pdo->exec($stmt) === false) {
                $err = $pdo->errorInfo();
                throw new \RuntimeException("SQL 执行错误：{$err[2]} (SQL: " . substr($stmt, 0, 120) . ')');
            }
        }
    }

    /** 确保升级记录表存在（幂等） */
    public static function ensureUpgradeLog(): void
    {
        if (Db::driver() !== 'mysql') return;
        try {
            Db::pdo()->exec("CREATE TABLE IF NOT EXISTS db_upgrades (
                id INT AUTO_INCREMENT PRIMARY KEY,
                file VARCHAR(255) NOT NULL COMMENT '升级包文件名',
                hash CHAR(32) NOT NULL COMMENT '文件 MD5，用于识别内容变更',
                applied_at DATETIME NOT NULL COMMENT '执行时间',
                applied_by VARCHAR(64) DEFAULT '' COMMENT '操作人',
                note TEXT COMMENT '备注'
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        } catch (\Throwable $e) {}
    }
    public static function isInstalled(): bool
    {
        if (Db::driver() !== 'mysql') return true; // file 模式无「安装」概念
        try {
            $cnt = (new \App\Models\AdminUser())->count();
            return $cnt > 0;
        } catch (\Throwable $e) {
            return false;
        }
    }

    /** 返回每张预期表的存在状态（mysql 模式） */
    public static function tableStatus(): array
    {
        if (Db::driver() !== 'mysql') return [];
        $pdo = Db::pdo();
        $exist = $pdo->query("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE()")->fetchAll(\PDO::FETCH_COLUMN);
        $all = [
            'categories', 'products', 'news', 'cases', 'pages', 'banners', 'admin_users',
            'crm_customers', 'crm_leads', 'crm_followups', 'crm_contacts',
            'psi_materials', 'psi_products', 'psi_suppliers', 'psi_purchases', 'psi_sales', 'psi_stock_moves',
            'settings', 'orders', 'payments',
        ];
        $out = [];
        foreach ($all as $t) { $out[$t] = in_array($t, $exist, true); }
        return $out;
    }

    /** 补齐各表新增列（防御性，schema.sql 已含，此处兜底供「数据升级」使用，幂等） */
    private static function ensureColumns($pdo, array &$msgs): void
    {
        $map = [
            'pages' => [
                'mode' => "VARCHAR(16) NOT NULL DEFAULT 'fixed'",
            ],
            'products' => [
                'mode' => "VARCHAR(16) NOT NULL DEFAULT 'fixed'",
            ],
            'news' => [
                'mode' => "VARCHAR(16) NOT NULL DEFAULT 'fixed'",
            ],
            'cases' => [
                'mode' => "VARCHAR(16) NOT NULL DEFAULT 'fixed'",
            ],
            'categories' => [
                'mode' => "VARCHAR(16) NOT NULL DEFAULT 'fixed'",
            ],
            'admin_users' => [
                'crm_role'   => "VARCHAR(20) DEFAULT 'none'",
                'psi_role'   => "VARCHAR(20) DEFAULT 'none'",
                'crm_perms'  => "TEXT",
                'psi_perms'  => "TEXT",
            ],
            'crm_customers' => [
                'customer_no' => "VARCHAR(40) DEFAULT ''",
                'industry'    => "VARCHAR(20) DEFAULT ''",
                'region'      => "VARCHAR(40) DEFAULT ''",
                'credit_limit'=> "DECIMAL(12,2) DEFAULT 0",
                'status'      => "VARCHAR(20) DEFAULT 'lead'",
            ],
            'crm_leads' => [
                'source'      => "VARCHAR(30) DEFAULT ''",
                'probability' => "TINYINT DEFAULT 0",
            ],
            'crm_followups' => [
                'way'    => "VARCHAR(20) DEFAULT ''",
                'result' => "VARCHAR(60) DEFAULT ''",
            ],
            'psi_materials' => [
                'composition' => "VARCHAR(60) DEFAULT ''",
                'weight_gsm'  => "DECIMAL(8,2) DEFAULT 0",
                'width_cm'    => "DECIMAL(8,2) DEFAULT 0",
                'color'       => "VARCHAR(40) DEFAULT ''",
                'batch_no'    => "VARCHAR(40) DEFAULT ''",
            ],
            'psi_products' => [
                'style_no' => "VARCHAR(40) DEFAULT ''",
                'color'    => "VARCHAR(40) DEFAULT ''",
                'size_run' => "VARCHAR(60) DEFAULT ''",
                'season'   => "VARCHAR(20) DEFAULT ''",
                'year'     => "VARCHAR(10) DEFAULT ''",
            ],
            'psi_suppliers' => [
                'type'       => "VARCHAR(20) DEFAULT ''",
                'grade'      => "VARCHAR(20) DEFAULT ''",
                'ontime_rate'=> "DECIMAL(5,2) DEFAULT 0",
                'qc_rate'    => "DECIMAL(5,2) DEFAULT 0",
            ],
            'psi_purchases' => [
                'batch_no'    => "VARCHAR(40) DEFAULT ''",
                'expected_at' => "VARCHAR(20) DEFAULT ''",
            ],
            'psi_sales' => [
                'region'   => "VARCHAR(40) DEFAULT ''",
                'batch_no' => "VARCHAR(40) DEFAULT ''",
            ],
            'psi_stock_moves' => [
                'batch_no' => "VARCHAR(40) DEFAULT ''",
            ],
        ];
        foreach ($map as $table => $cols) {
            try {
                $have = $pdo->query("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='{$table}'")->fetchAll(\PDO::FETCH_COLUMN);
            } catch (\Throwable $e) {
                continue;
            }
            foreach ($cols as $c => $def) {
                if (!in_array($c, $have, true)) {
                    try {
                        $pdo->exec("ALTER TABLE `{$table}` ADD COLUMN `{$c}` {$def}");
                        $msgs[] = "已添加 {$table}.{$c}";
                    } catch (\Throwable $e) {}
                }
            }
        }
    }

    private static function modelMap(): array
    {
        return [
            'categories' => new \App\Models\Category(),
            'products'   => new \App\Models\Product(),
            'news'       => new \App\Models\News(),
            'cases'      => new \App\Models\CustomerCase(),
            'pages'      => new \App\Models\Page(),
            'banners'    => new \App\Models\Banner(),
            'admin_users'=> new \App\Models\AdminUser(),
            'settings'   => new \App\Models\Setting(),
            'orders'     => new \App\Models\Order(),
            'payments'   => new \App\Models\Payment(),
            'crm_customers'  => new \App\Models\CRM\Customer(),
            'crm_leads'      => new \App\Models\CRM\Lead(),
            'crm_followups'  => new \App\Models\CRM\FollowUp(),
            'crm_contacts'   => new \App\Models\CRM\Contact(),
            'psi_suppliers'  => new \App\Models\PSI\Supplier(),
            'psi_materials'  => new \App\Models\PSI\Material(),
            'psi_products'   => new \App\Models\PSI\Product(),
            'psi_purchases'  => new \App\Models\PSI\Purchase(),
            'psi_sales'      => new \App\Models\PSI\Sales(),
            'psi_stock_moves'=> new \App\Models\PSI\StockMove(),
        ];
    }
}
