<?php
namespace Gestoreasy\Core\Auth;

use Gestoreasy\Core\Http\Request;
use Gestoreasy\Core\Http\Response;
use Gestoreasy\Core\Tenant\TenantContext;
use Gestoreasy\Core\Admin\ConnectionManager as AdminDb;
use Gestoreasy\Core\Audit\AuditLogger;

class PlanAccessMiddleware {
    /**
     * Verifica se o módulo/feature está habilitado pelo plano do tenant.
     * Bypass: sessões impersonadas, usuários com role 'admin' ou email admin@local.
     */
    public static function requireModule(string $module): callable {
        return function (Request $req) use ($module) {
            $user = UserContext::get($req) ?: [];
            if (!$user) { return new Response(401, ['error' => 'Unauthorized']); }
            // Bypass para impersonate/admin
            $roles = (array)($user['roles'] ?? []);
            $email = (string)($user['email'] ?? '');
            $impersonated = !empty($user['impersonated']);
            $hasAdminRole = in_array('admin', $roles, true);
            $isAdminEmail = strtolower($email) === 'admin@local';
            
            if ($impersonated || $hasAdminRole || $isAdminEmail) {
                return null;
            }

            $tenantId = TenantContext::getTenantId();
            try {
                $pdo = AdminDb::db();
                // Obter registro do tenant
                $tStmt = $pdo->prepare('SELECT tenant_id, plan_id, access_enabled FROM tenants WHERE tenant_id = ? LIMIT 1');
                $tStmt->execute([$tenantId]);
                $tenant = $tStmt->fetch();
                if (!$tenant) {
                    return new Response(403, ['error' => ['code' => 'tenant_not_registered', 'message' => 'Tenant não registrado no admin', 'tenant' => $tenantId]]);
                }
                if ((int)($tenant['access_enabled'] ?? 1) === 0) {
                    self::logDenied($req, $user, $module, 'tenant_access_disabled');
                    return new Response(403, ['error' => ['code' => 'tenant_access_disabled', 'message' => 'Acesso do tenant desabilitado', 'tenant' => $tenantId]]);
                }
                $planId = (int)($tenant['plan_id'] ?? 0);
                $modules = [];
                $planName = null;
                if ($planId > 0) {
                    $pStmt = $pdo->prepare('SELECT id, name, modules_json FROM plans WHERE id = ? LIMIT 1');
                    $pStmt->execute([$planId]);
                    $plan = $pStmt->fetch();
                    $planName = (string)($plan['name'] ?? null);
                    $modsJson = (string)($plan['modules_json'] ?? '');
                    if ($modsJson !== '') {
                        $decoded = json_decode($modsJson, true);
                        if (is_array($decoded)) { $modules = array_values($decoded); }
                    }
                }
                // Overrides por tenant (habilitar/desabilitar)
                $oStmt = $pdo->prepare('SELECT module, enabled FROM tenant_modules WHERE tenant_id = ?');
                $oStmt->execute([$tenantId]);
                $overrides = $oStmt->fetchAll();
                $enabledMap = [];
                foreach ($modules as $m) { $enabledMap[(string)$m] = true; }
                foreach ($overrides as $ov) {
                    $enabledMap[(string)($ov['module'] ?? '')] = ((int)($ov['enabled'] ?? 1) === 1);
                }
                // Privilégios básicos sempre habilitados
                foreach (self::basicModules() as $bm) { $enabledMap[$bm] = true; }

                $isEnabled = ($enabledMap[$module] ?? false) === true;
                if (!$isEnabled) {
                    self::logDenied($req, $user, $module, 'module_disabled');
                    return new Response(403, ['error' => [
                        'code' => 'module_disabled',
                        'message' => 'Módulo não permitido pelo plano deste tenant',
                        'module' => $module,
                        'plan' => $planName,
                        'tenant' => $tenantId
                    ]]);
                }
                return null;
            } catch (\Throwable $e) {
                // Em caso de falha no admin DB, não bloquear, mas registrar
                self::logDenied($req, $user, $module, 'plan_check_error', $e->getMessage());
                return null; // tolerante
            }
        };
    }

    /** Versão para páginas HTML. Mantém a mesma lógica e retorna 403 em caso de bloqueio. */
    public static function requireModuleHtml(string $module): callable {
        return function (Request $req) use ($module) {
            $res = self::requireModule($module)($req);
            if ($res instanceof Response) { return $res; }
            return null;
        };
    }

    private static function basicModules(): array {
        // Lista mínima de módulos sempre disponíveis em qualquer plano
        return [
            'clientes', 'produtos', 'vendas', 'compras', 'estoque', 'servicos', 'cadastros'
        ];
    }

    private static function logDenied(Request $req, array $user, string $module, string $code, ?string $detail = null): void {
        try {
            $email = (string)($user['email'] ?? '');
            AuditLogger::log('access_denied', 'module', null, null, null, [
                'module' => $module,
                'code' => $code,
                'endpoint' => $req->getPath(),
                'user_email' => $email,
                'detail' => $detail,
            ]);
        } catch (\Throwable $e) { /* silencioso */ }
        // Registrar também no admin DB
        try {
            $admin = AdminDb::db();
            $meta = json_encode([
                'tenant' => TenantContext::getTenantId(),
                'module' => $module,
                'code' => $code,
                'endpoint' => $req->getPath(),
                'user_email' => (string)($user['email'] ?? '')
            ]);
            $al = $admin->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?,?,?,?,?)');
            $al->execute(['tenant_access_denied', 'module', null, $meta, $req->getClientIp()]);
        } catch (\Throwable $e) { /* silencioso */ }
    }
}