<?php
namespace Gestoreasy\Core\Tenant;

use PDO;
use Gestoreasy\Core\Config\Config;
use Gestoreasy\Core\Audit\AuditLogger;

class TenantAdmin {
    private static function serverPdo(): PDO {
        $dbHost = Config::get('DB_HOST', '127.0.0.1');
        $dbUser = Config::get('DB_USERNAME', 'root');
        $dbPass = Config::get('DB_PASSWORD', '');
        $dsn = "mysql:host=$dbHost;charset=utf8mb4";
        return new PDO($dsn, $dbUser, $dbPass, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    }

    public static function listTenants(): array {
        $prefix = Config::get('DB_BASE_PREFIX', 'gestoreasy_tenant_');
        $pdo = self::serverPdo();
        $rows = $pdo->query('SHOW DATABASES')->fetchAll();
        $tenants = [];
        foreach ($rows as $row) {
            $db = $row['Database'] ?? $row['database'] ?? null;
            if ($db && str_starts_with($db, $prefix)) {
                $tenants[] = substr($db, strlen($prefix));
            }
        }
        sort($tenants);
        return $tenants;
    }

    public static function createTenant(string $tenantId): array {
        $tenantId = trim($tenantId);
        if ($tenantId === '' || !preg_match('/^[a-zA-Z0-9_\-]+$/', $tenantId)) {
            throw new \InvalidArgumentException('tenant_id inválido');
        }
        $prefix = Config::get('DB_BASE_PREFIX', 'gestoreasy_tenant_');
        $dbName = $prefix . $tenantId;
        $pdo = self::serverPdo();
        $pdo->exec("CREATE DATABASE IF NOT EXISTS `$dbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");

        // Rodar migrações conhecidas e garantir esquema de Auth
        self::runMigrations($tenantId);
        $tenantPdo = ConnectionManager::forTenant($tenantId);
        self::ensureBaseSchema($tenantPdo);
        self::seedDefaultAdmin($tenantPdo);

        return ['created' => true, 'tenant' => $tenantId];
    }

    public static function runMigrations(string $tenantId): array {
        // Garante que o DB do tenant exista: se não existir, cria e reconecta
        try {
            $pdo = ConnectionManager::forTenant($tenantId);
        } catch (\PDOException $e) {
            $msg = $e->getMessage();
            $code = (int)($e->errorInfo[1] ?? $e->getCode());
            if ($code === 1049 || str_contains(strtolower($msg), 'unknown database')) {
                $prefix = Config::get('DB_BASE_PREFIX', 'gestoreasy_tenant_');
                $dbName = $prefix . $tenantId;
                try {
                    $server = self::serverPdo();
                    $server->exec("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
                } catch (\Throwable $e2) {
                    // Se não conseguir criar, propaga o erro original
                    throw $e;
                }
                // Tenta novamente a conexão do tenant
                $pdo = ConnectionManager::forTenant($tenantId);
            } else {
                throw $e;
            }
        }
        // Garantir engine e charset para sessões MySQL
        try { $pdo->exec('SET default_storage_engine=InnoDB'); } catch (\Throwable $e) {}
        try { $pdo->exec("SET NAMES 'utf8mb4'"); } catch (\Throwable $e) {}
        // Tabela de rastreamento de migrações
        try { $pdo->exec('CREATE TABLE IF NOT EXISTS schema_migrations (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            module VARCHAR(100) NOT NULL,
            filename VARCHAR(190) NOT NULL,
            applied_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_module_file (module, filename)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'); } catch (\Throwable $e) {}
        $modulesPath = __DIR__ . '/../../../modules';
        $applied = [];
        if (is_dir($modulesPath)) {
            // Executar migrações módulo a módulo com prioridade de dependências
            $moduleDirs = glob($modulesPath . '/*', GLOB_ONLYDIR) ?: [];
            // Priorizar módulos que provêm tabelas base (ex.: Produtos antes de Compras)
            usort($moduleDirs, function($a, $b) {
                $pa = basename($a);
                $pb = basename($b);
                $priority = [
                    'Produtos' => 10,
                    'Compras' => 20,
                ];
                $wa = $priority[$pa] ?? 100;
                $wb = $priority[$pb] ?? 100;
                if ($wa === $wb) return strcmp($pa, $pb);
                return $wa <=> $wb;
            });
            foreach ($moduleDirs as $moduleDir) {
                $migrationsDir = $moduleDir . '/migrations';
                if (!is_dir($migrationsDir)) continue;
                $files = glob($migrationsDir . '/*.sql') ?: [];
                // Ordenar: 'initial.sql' primeiro, depois por prefixo numérico ascendente, em seguida alfabético
                usort($files, function ($a, $b) {
                    $wa = self::migrationWeight(basename($a));
                    $wb = self::migrationWeight(basename($b));
                    if ($wa === $wb) return strcmp($a, $b);
                    return $wa <=> $wb;
                });
                $moduleName = basename($moduleDir);
                foreach ($files as $file) {
                    $sql = file_get_contents($file);
                    if ($sql && trim($sql) !== '') {
                        // Pular se já aplicada
                        try {
                            $chk = $pdo->prepare('SELECT 1 FROM schema_migrations WHERE module = ? AND filename = ? LIMIT 1');
                            $chk->execute([$moduleName, basename($file)]);
                            $appliedRow = $chk->fetch();
                            try { $chk->closeCursor(); } catch (\Throwable $ignore) {}
                            if ($appliedRow) { continue; }
                        } catch (\Throwable $e) {}
                        // Executa cada declaração com tolerância a falhas menores
                        foreach (self::splitSqlStatements($sql) as $stmt) {
                            if (trim($stmt) === '') continue;
                            try {
                                $pdo->exec($stmt);
                            } catch (\Throwable $eStmt) {
                                // Ignorar erro e seguir com demais declarações/migrações
                                try { AuditLogger::logUpdate('schema_migrations', null, [], ['tenant' => $tenantId, 'module' => $moduleName, 'filename' => basename($file), 'stmt_error' => $eStmt->getMessage()]); } catch (\Throwable $eLog) {}
                            }
                        }
                        try {
                            $ins = $pdo->prepare('INSERT INTO schema_migrations (module, filename) VALUES (?, ?)');
                            $ins->execute([$moduleName, basename($file)]);
                            try { $ins->closeCursor(); } catch (\Throwable $ignore) {}
                        } catch (\Throwable $e) {}
                        $applied[] = $moduleName . ':' . basename($file);
                        // Log auditoria da migração aplicada
                        try { AuditLogger::logUpdate('schema_migrations', null, [], ['tenant' => $tenantId, 'module' => $moduleName, 'filename' => basename($file)]); } catch (\Throwable $e) {}
                    }
                }
            }
        }
        // Garantir esquema base mínimo (tolerante a já existir)
        try {
            self::ensureBaseSchema($pdo);
        } catch (\Throwable $e) {
            // Ignorar erros de tabela já existente ou incompatibilidades menores
        }
        return ['migrations' => $applied];
    }

    private static function migrationWeight(string $filename): int {
        $lower = strtolower($filename);
        if (str_starts_with($lower, 'initial')) return 0;
        if (preg_match('/^(\d+)/', $lower, $m)) return (int)$m[1];
        return 10000; // arquivos não numerados por último
    }

    private static function splitSqlStatements(string $sql): array {
        // Split simples por ';' (não trata casos complexos de delimitadores)
        $parts = preg_split('/;\s*\n|;\s*$/m', $sql);
        return $parts ?: [];
    }

    private static function ensureBaseSchema(PDO $pdo): void {
        // Tabelas básicas para Auth/RBAC e auditoria
        try { $pdo->exec('CREATE TABLE IF NOT EXISTS users (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            email VARCHAR(190) UNIQUE NOT NULL,
            password_hash VARCHAR(255) NOT NULL,
            name VARCHAR(190) NOT NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'); } catch (\Throwable $e) {}

        try { $pdo->exec('CREATE TABLE IF NOT EXISTS roles (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(100) UNIQUE NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'); } catch (\Throwable $e) {}

        try { $pdo->exec('CREATE TABLE IF NOT EXISTS permissions (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(150) UNIQUE NOT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'); } catch (\Throwable $e) {}

        try { $pdo->exec('CREATE TABLE IF NOT EXISTS user_roles (
            user_id BIGINT UNSIGNED NOT NULL,
            role_id BIGINT UNSIGNED NOT NULL,
            PRIMARY KEY (user_id, role_id),
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
            FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'); } catch (\Throwable $e) {}

        try { $pdo->exec('CREATE TABLE IF NOT EXISTS role_permissions (
            role_id BIGINT UNSIGNED NOT NULL,
            permission_id BIGINT UNSIGNED NOT NULL,
            PRIMARY KEY (role_id, permission_id),
            FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
            FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'); } catch (\Throwable $e) {}

        try { $pdo->exec('CREATE TABLE IF NOT EXISTS audit_logs (
            id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            user_id BIGINT UNSIGNED NULL,
            action VARCHAR(100) NOT NULL,
            resource VARCHAR(100) NOT NULL,
            resource_id BIGINT UNSIGNED NULL,
            old_data JSON NULL,
            new_data JSON NULL,
            metadata JSON NULL,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            INDEX (resource, resource_id),
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'); } catch (\Throwable $e) {}
    }

    public static function seedDefaultAdmin(PDO $pdo): array {
        // Criar permissões padrão
        $defaultPerms = [
            'clientes.read','clientes.create','clientes.update','clientes.delete',
            'produtos.read','produtos.create','produtos.update','produtos.delete',
            'compras.read','compras.create','compras.update','compras.delete',
            'cotacoes.read','cotacoes.create','cotacoes.update','cotacoes.delete',
            'estoque.read','estoque.update',
            'wms.read','wms.create','wms.update','wms.delete',
            'vendas.read','vendas.create','vendas.update','vendas.delete',
            'pdv.read','auth.admin'
        ];
        foreach ($defaultPerms as $p) {
            $stmt = $pdo->prepare('INSERT IGNORE INTO permissions (name) VALUES (?)');
            $stmt->execute([$p]);
            try { $stmt->closeCursor(); } catch (\Throwable $ignore) {}
        }
        // Criar role admin
        $pdo->exec("INSERT IGNORE INTO roles (name) VALUES ('admin')");
        $rs = $pdo->query("SELECT id FROM roles WHERE name = 'admin' LIMIT 1");
        $roleRow = $rs->fetch();
        try { $rs->closeCursor(); } catch (\Throwable $ignore) {}
        $roleId = $roleRow['id'] ?? null;
        if ($roleId) {
            $ps = $pdo->query('SELECT id FROM permissions');
            $permRows = $ps->fetchAll();
            try { $ps->closeCursor(); } catch (\Throwable $ignore) {}
            foreach ($permRows as $row) {
                $stmt = $pdo->prepare('INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?, ?)');
                $stmt->execute([$roleId, (int)$row['id']]);
                try { $stmt->closeCursor(); } catch (\Throwable $ignore) {}
            }
        }
        // Criar usuário admin padrão
        $email = Config::get('DEFAULT_ADMIN_EMAIL', 'admin@local');
        $name = Config::get('DEFAULT_ADMIN_NAME', 'Admin');
        $pass = Config::get('DEFAULT_ADMIN_PASSWORD', 'admin123');
        $hash = password_hash($pass, PASSWORD_BCRYPT);
        $stmt = $pdo->prepare('INSERT IGNORE INTO users (email, password_hash, name) VALUES (?, ?, ?)');
        $stmt->execute([$email, $hash, $name]);
        $us = $pdo->query("SELECT id FROM users WHERE email = '" . addslashes($email) . "' LIMIT 1");
        $urow = $us->fetch();
        try { $us->closeCursor(); } catch (\Throwable $ignore) {}
        $userId = $urow['id'] ?? null;
        if ($userId && $roleId) {
            $stmt = $pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)');
            $stmt->execute([(int)$userId, (int)$roleId]);
            try { $stmt->closeCursor(); } catch (\Throwable $ignore) {}
        }
        return ['admin_seeded' => true, 'email' => $email, 'password' => $pass];
    }
}