<?php
namespace app\controller;

use app\BaseController;
use think\facade\Db;
use think\facade\Request;
use think\response\Json;
use think\facade\Cache;
use app\service\LicenseService;

class Install extends BaseController
{
    private $lockFile;
    private $envFile;

    public function initialize()
    {
        $this->lockFile = root_path() . 'install/install.lock';
        $this->envFile = root_path() . '.env';
    }

    public function checkLock(): Json
    {
        if (file_exists($this->lockFile)) {
            return json(['code' => 200, 'msg' => 'Already installed', 'data' => ['installed' => true]]);
        }
        return json(['code' => 200, 'msg' => 'Not installed', 'data' => ['installed' => false]]);
    }

    public function saveLicense(): Json
    {
        if (file_exists($this->lockFile)) {
             return json(['code' => 403, 'msg' => 'System already installed']);
        }

        $data = Request::post();
        $key = $data['key'] ?? '';

        if (empty($key)) {
            return json(['code' => 400, 'msg' => 'Please enter license key']);
        }

        $keyPath = root_path() . 'config/license_feature.key';
        
        try {
            // Ensure config directory exists
            if (!is_dir(dirname($keyPath))) {
                mkdir(dirname($keyPath), 0755, true);
            }

            // Save key to file
            file_put_contents($keyPath, $key);
            
            // Clear caches to force re-validation
            Cache::delete('license_validate_result');
            if (file_exists(root_path() . 'config/license_data.cache')) {
                unlink(root_path() . 'config/license_data.cache');
            }
            
            // Validate
            $service = new LicenseService();
            $result = $service->validateLicense();
            
            if ($result === true) {
                // Return encrypted license data with code:200 for frontend validation
                $rawCache = $service->loadCache();
                $encryptedData = $rawCache['encrypted_data'] ?? '';
                
                return json([
                    'code' => 200, 
                    'msg' => 'License valid', 
                    'data' => [
                        'code' => 200, // Required for frontend logic (res.code === 200)
                        'encrypted_data' => $encryptedData
                    ]
                ]);
            } else {
                return json($result);
            }

        } catch (\Exception $e) {
            return json(['code' => 500, 'msg' => 'License validation failed: ' . $e->getMessage()]);
        }
    }

    public function checkEnv(): Json
    {
        if (file_exists($this->lockFile)) {
             return json(['code' => 403, 'msg' => 'System already installed']);
        }

        $mysqlVersionStatus = extension_loaded('pdo_mysql');
        $mysqlVersionCurrent = '未检测';

        if ($mysqlVersionStatus) {
            $mysqlVersionCurrent = '安装时检测';
        }

        $items = [
            'php_version' => [
                'name' => 'PHP 版本 >= 8.0',
                'status' => version_compare(PHP_VERSION, '8.0.0', '>='),
                'current' => PHP_VERSION,
                'required' => '8.0.0'
            ],
            'pdo' => [
                'name' => 'PDO 扩展',
                'status' => extension_loaded('pdo'),
            ],
            'pdo_mysql' => [
                'name' => 'PDO MySQL 扩展',
                'status' => extension_loaded('pdo_mysql'),
            ],
            'mysql_version' => [
                'name' => 'MySQL 版本 >= 5.7',
                'status' => $mysqlVersionStatus,
                'current' => $mysqlVersionCurrent,
                'required' => '5.7.0'
            ],
            'curl' => [
                'name' => 'CURL 扩展',
                'status' => extension_loaded('curl'),
            ],
            'openssl' => [
                'name' => 'OpenSSL 扩展',
                'status' => extension_loaded('openssl'),
            ],
            'fileinfo' => [
                'name' => 'FileInfo 扩展',
                'status' => extension_loaded('fileinfo'),
            ],
             'dir_write' => [
                'name' => '目录权限 (runtime, public)',
                'status' => is_writable(root_path() . 'runtime') && is_writable(public_path()),
            ]
        ];
        
        $allPass = true;
        foreach ($items as $item) {
            if (!$item['status']) {
                $allPass = false;
                break;
            }
        }

        return json(['code' => 200, 'data' => ['items' => $items, 'pass' => $allPass]]);
    }

    private function normalizeMysqlVersion(string $version): string
    {
        if (preg_match('/\d+(?:\.\d+){0,2}/', $version, $matches)) {
            return $matches[0];
        }

        return '';
    }

    private function checkMysqlVersion(\PDO $pdo, string $requiredVersion = '5.7.0'): array
    {
        $version = (string) $pdo->query('SELECT VERSION()')->fetchColumn();
        $normalizedVersion = $this->normalizeMysqlVersion($version);

        if ($normalizedVersion === '') {
            return [
                'status' => false,
                'current' => $version,
                'required' => $requiredVersion,
                'message' => '无法识别当前 MySQL 版本，请确认数据库版本不低于 5.7。',
            ];
        }

        if (version_compare($normalizedVersion, $requiredVersion, '<')) {
            return [
                'status' => false,
                'current' => $normalizedVersion,
                'required' => $requiredVersion,
                'message' => '当前 MySQL 版本为 ' . $normalizedVersion . '，系统要求 MySQL 版本不低于 ' . $requiredVersion . '。',
            ];
        }

        return [
            'status' => true,
            'current' => $normalizedVersion,
            'required' => $requiredVersion,
            'message' => 'MySQL version is supported',
        ];
    }

    public function testDb(): Json
    {
        if (file_exists($this->lockFile)) {
             return json(['code' => 403, 'msg' => 'System already installed']);
        }

        $data = Request::post();
        if (empty($data['hostname']) || empty($data['database']) || empty($data['username'])) {
            return json(['code' => 400, 'msg' => 'Please fill in all database fields']);
        }

        try {
            $dsn = "mysql:host={$data['hostname']};port={$data['hostport']};dbname={$data['database']};charset=utf8mb4";
            $pdo = new \PDO($dsn, $data['username'], $data['password']);
            $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

            $mysqlVersionCheck = $this->checkMysqlVersion($pdo);
            if (!$mysqlVersionCheck['status']) {
                return json(['code' => 400, 'msg' => $mysqlVersionCheck['message']]);
            }

            return json([
                'code' => 200,
                'msg' => 'Connection successful',
                'data' => [
                    'mysql_version' => $mysqlVersionCheck['current']
                ]
            ]);
        } catch (\PDOException $e) {
            return json(['code' => 400, 'msg' => 'Connection failed: ' . $e->getMessage()]);
        }
    }

    public function install(): Json
    {
        if (file_exists($this->lockFile)) {
             return json(['code' => 403, 'msg' => 'System already installed']);
        }

        $data = Request::post();
        
        // 1. Write .env
        $envContent = <<<EOT
APP_DEBUG=false

DB_TYPE=mysql
DB_HOST={$data['hostname']}
DB_NAME={$data['database']}
DB_USER={$data['username']}
DB_PASS={$data['password']}
DB_PORT={$data['hostport']}
DB_CHARSET=utf8mb4

[APP]
DEFAULT_TIMEZONE=Asia/Shanghai

[LANG]
default_lang=zh-cn
EOT;
        
        try {
            file_put_contents($this->envFile, $envContent);
        } catch (\Exception $e) {
            return json(['code' => 500, 'msg' => 'Failed to write .env file: ' . $e->getMessage()]);
        }

        $onlyConfig = $data['only_config'] ?? false;
        if ($onlyConfig) {
            file_put_contents($this->lockFile, 'Installed at ' . date('Y-m-d H:i:s'));
            return json(['code' => 200, 'msg' => 'Configuration updated successfully']);
        }

        // 2. Import SQL
        $sqlFile = root_path() . 'install/install.sql';
        if (!file_exists($sqlFile)) {
             return json(['code' => 500, 'msg' => 'install.sql file not found in install directory.']);
        }
        
        try {
            $dsn = "mysql:host={$data['hostname']};port={$data['hostport']};dbname={$data['database']};charset=utf8mb4";
            $pdo = new \PDO($dsn, $data['username'], $data['password'], [
                \PDO::MYSQL_ATTR_MULTI_STATEMENTS => true
            ]);
            $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

            $mysqlVersionCheck = $this->checkMysqlVersion($pdo);
            if (!$mysqlVersionCheck['status']) {
                return json(['code' => 400, 'msg' => $mysqlVersionCheck['message']]);
            }
            
            $sqlContent = file_get_contents($sqlFile);
            $importStats = $this->importSqlStatements($pdo, $sqlContent);
            
            // Automatically ensure default data exists (Roles, Settings, Crontab)
            $this->ensureDefaultData($pdo);

            $message = 'Installation successful';
            $skippedTables = (int) ($importStats['skipped_existing_tables'] ?? 0);
            $skippedConstraints = (int) ($importStats['skipped_existing_constraints'] ?? 0);
            if ($skippedTables > 0 || $skippedConstraints > 0) {
                $parts = [];
                if ($skippedTables > 0) {
                    $parts[] = $skippedTables . ' existing tables';
                }
                if ($skippedConstraints > 0) {
                    $parts[] = $skippedConstraints . ' existing indexes/primary keys';
                }
                $message = 'Installation successful, skipped ' . implode(' and ', $parts);
            }
            
        } catch (\Exception $e) {
             return json(['code' => 500, 'msg' => 'Database import failed: ' . $e->getMessage()]);
        }
        
        // 3. Create install.lock
        // file_put_contents($this->lockFile, 'Installed at ' . date('Y-m-d H:i:s'));
        
        return json(['code' => 200, 'msg' => $message ?? 'Installation successful']);
    }

    private function importSqlStatements(\PDO $pdo, string $sqlContent): array
    {
        $lines = preg_split('/\r\n|\r|\n/', $sqlContent);
        $cleanedLines = [];
        $inBlockComment = false;

        foreach ($lines as $line) {
            $trimmed = trim($line);

            if ($inBlockComment) {
                if (strpos($trimmed, '*/') !== false) {
                    $inBlockComment = false;
                }
                continue;
            }

            if ($trimmed === '') {
                $cleanedLines[] = $line;
                continue;
            }

            if (preg_match('/^(--|#)/', $trimmed)) {
                continue;
            }

            if (preg_match('/^\/\*/', $trimmed)) {
                if (strpos($trimmed, '*/') === false) {
                    $inBlockComment = true;
                }
                continue;
            }

            $cleanedLines[] = $line;
        }

        $statements = preg_split('/;\s*(?:\r?\n|$)/', implode(PHP_EOL, $cleanedLines));
        $executed = 0;
        $skippedExistingTables = 0;
        $skippedExistingConstraints = 0;

        foreach ($statements as $statement) {
            $sql = trim($statement);
            if ($sql === '') {
                continue;
            }

            if (preg_match('/^(SET\s|START TRANSACTION|COMMIT|ROLLBACK|LOCK TABLES|UNLOCK TABLES)/i', $sql)) {
                continue;
            }

            try {
                $pdo->exec($sql);
                $executed++;
            } catch (\PDOException $e) {
                $errorInfo = $e->errorInfo ?? [];
                $mysqlCode = isset($errorInfo[1]) ? (int)$errorInfo[1] : 0;
                $message = $e->getMessage();
                $isCreateTable = preg_match('/^CREATE\s+TABLE/i', $sql) === 1;
                $isAlterTable = preg_match('/^ALTER\s+TABLE/i', $sql) === 1;
                $isExistingTable = $mysqlCode === 1050 || stripos($message, 'Base table or view already exists') !== false;
                $isExistingConstraint = in_array($mysqlCode, [1061, 1068], true)
                    || stripos($message, 'Duplicate key name') !== false
                    || stripos($message, 'Multiple primary key defined') !== false;

                if ($isCreateTable && $isExistingTable) {
                    $skippedExistingTables++;
                    continue;
                }

                if ($isAlterTable && $isExistingConstraint) {
                    $skippedExistingConstraints++;
                    continue;
                }

                throw $e;
            }
        }

        return [
            'executed' => $executed,
            'skipped_existing_tables' => $skippedExistingTables,
            'skipped_existing_constraints' => $skippedExistingConstraints,
        ];
    }


    private function ensureDefaultData(\PDO $pdo)
    {
        // 1. Auth Group
        $stmt = $pdo->query("SELECT id FROM auth_groups WHERE id = 1");
        if (!$stmt->fetch()) {
            $sql = "INSERT INTO `auth_groups` (`id`, `name`, `description`, `rules`, `status`, `create_time`, `update_time`) VALUES (1, '超级管理员', '拥有所有权限，不可删除和编辑', '*', 1, NOW(), NOW())";
            $pdo->exec($sql);
        }

        // 2. System Settings
        $defaults = [
            'site_name' => 'Entropy',
            'site_desc' => 'Entropy System',
            'site_logo' => '',
            'site_favicon' => '',
            'logistics_auto_complete_days' => '0',
        ];
        
        $stmt = $pdo->prepare("SELECT `key` FROM system_settings WHERE `key` = ?");
        $insertStmt = $pdo->prepare("INSERT INTO `system_settings` (`key`, `value`, `create_time`, `update_time`) VALUES (?, ?, NOW(), NOW())");

        foreach ($defaults as $key => $value) {
            $stmt->execute([$key]);
            if (!$stmt->fetch()) {
                $insertStmt->execute([$key, $value]);
            }
        }

        // 3. System Crontab
        $tasks = [
            [
                'title' => '自动生成提现批次',
                'content' => 'CreateWithdrawalBatch',
                'status' => 1,
                'remark' => 'Automatically create withdrawal batches'
            ],
            [
                'title' => '释放超时订单',
                'content' => 'ReleaseTimeoutOrders',
                'status' => 1,
                'remark' => 'Release inventory for unpaid timeout orders'
            ],
            [
                'title' => '投诉超时通知',
                'content' => 'ComplaintTimeoutNotify',
                'status' => 1,
                'remark' => 'Notify admin/seller of timeout complaints'
            ],
            [
                'title' => '更新订单物流',
                'content' => 'UpdateOrderLogistics',
                'status' => 1,
                'remark' => 'Update unsigned order logistics by logistics plugin'
            ],
            [
                'title' => '快递签收自动完成订单',
                'content' => 'CompleteSignedLogisticsOrders',
                'status' => 1,
                'remark' => 'Auto complete transporting orders after logistics signed'
            ]
        ];

        $checkTaskStmt = $pdo->prepare("SELECT id FROM system_crontab WHERE content = ?");
        $insertTaskStmt = $pdo->prepare("INSERT INTO `system_crontab` (`title`, `content`, `status`, `remark`, `create_time`, `update_time`) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())");

        foreach ($tasks as $task) {
            $checkTaskStmt->execute([$task['content']]);
            if (!$checkTaskStmt->fetch()) {
                $insertTaskStmt->execute([$task['title'], $task['content'], $task['status'], $task['remark']]);
            }
        }
    }

    public function createAdmin(): Json
    {
        if (file_exists($this->lockFile)) {
             return json(['code' => 403, 'msg' => 'System already installed']);
        }
        
        $data = Request::post();
        $username = $data['username'] ?? '';
        $password = $data['password'] ?? '';
        $confirm = $data['confirm_password'] ?? '';
        $email = $data['email'] ?? '';
        $qq = $data['qq'] ?? '';
        
        if (empty($username) || empty($password)) {
            return json(['code' => 400, 'msg' => 'Username and password are required']);
        }
        
        if ($password !== $confirm) {
            return json(['code' => 400, 'msg' => 'Passwords do not match']);
        }
        
        try {
            $exists = Db::name('admin_users')->where('username', $username)->find();
            if ($exists) {
                Db::name('admin_users')->where('username', $username)->update([
                    'password' => password_hash($password, PASSWORD_DEFAULT),
                    'email' => $email,
                    'qq' => $qq,
                    'group_id' => 1,
                    'update_time' => date('Y-m-d H:i:s')
                ]);
            } else {
                Db::name('admin_users')->insert([
                    'username' => $username,
                    'password' => password_hash($password, PASSWORD_DEFAULT),
                    'nickname' => 'Super Admin',
                    'avatar' => '',
                    'email' => $email,
                    'qq' => $qq,
                    'status' => 1,
                    'role' => 1,
                    'group_id' => 1,
                    'create_time' => date('Y-m-d H:i:s'),
                    'update_time' => date('Y-m-d H:i:s')
                ]);
            }
            
            file_put_contents($this->lockFile, 'Installed at ' . date('Y-m-d H:i:s'));
            
            return json(['code' => 200, 'msg' => 'Admin created and installation completed']);
            
        } catch (\Exception $e) {
            return json(['code' => 500, 'msg' => 'Failed to create admin: ' . $e->getMessage()]);
        }
    }
}
