<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Request;
use App\Core\Response;
use App\Core\Session;
use App\Core\TenantManager;
use App\Helpers\SkinHelper;
use App\Helpers\UrlHelper;
use PDO;

/**
 * Controlador Base
 * Todos os controllers devem extender esta classe
 */
abstract class BaseController
{
    protected Request $request;
    protected Response $response;
    protected Session $session;
    protected TenantManager $tenantManager;
    protected ?PDO $db = null;

    public function __construct(Request $request, Response $response)
    {
        $this->request = $request;
        $this->response = $response;

        $config = require \ROOT_PATH . '/config/app.php';
        $this->session = new Session($config['session']);
        $this->tenantManager = new TenantManager();

        // MODO SIMPLES: Se não tem DB_TENANT_PREFIX, conecta direto ao banco único
        if (empty($_ENV['DB_TENANT_PREFIX'])) {
            // Modo simples - sem multi-tenant
            $dbConfig = require \ROOT_PATH . '/config/database.php';
            $master = $dbConfig['master'];

            $hosts = [$master['host']];
            if ($master['host'] === 'localhost') {
                $hosts[] = '127.0.0.1';
            }

            $conectado = false;
            $ultimaExcecao = null;

            foreach (array_unique($hosts) as $host) {
                try {
                    // Adicionar default_auth para evitar problemas com plugins inexistentes
                    $dsn = sprintf(
                        '%s:host=%s;port=%d;dbname=%s;charset=%s',
                        $master['driver'],
                        $host,
                        $master['port'],
                        $master['database'],
                        $master['charset']
                    );

                    $this->db = new PDO($dsn, $master['username'], $master['password'], $master['options']);
                    $conectado = true;
                    break;
                } catch (\Exception $e) {
                    $ultimaExcecao = $e;
                    error_log(sprintf('Erro ao conectar ao banco usando host "%s": %s', $host, $e->getMessage()));
                }
            }

            if (!$conectado) {
                throw $ultimaExcecao ?? new \RuntimeException('Não foi possível conectar ao banco de dados.');
            }
        } else {
            // Modo multi-tenant - recupera conexão do tenant da sessão
            $tenantId = $this->session->getTenantId();
            if ($tenantId) {
                try {
                    $masterDb = $this->tenantManager->getMasterConnection();
                    $stmt = $masterDb->prepare("SELECT * FROM tenants WHERE id = :id LIMIT 1");
                    $stmt->execute(['id' => $tenantId]);
                    $tenant = $stmt->fetch();

                    if ($tenant) {
                        $this->tenantManager->setCurrentTenant($tenant);
                        $this->db = $this->tenantManager->getTenantConnection();
                    }
                } catch (\Exception $e) {
                    error_log("Erro ao conectar ao tenant: " . $e->getMessage());
                }
            }
        }

        // Inicializar SkinHelper com a conexão do banco
        if ($this->db) {
            SkinHelper::setDatabase($this->db);
        }
    }

    /**
     * Renderiza uma view
     */
    protected function view(string $view, array $data = []): void
    {
        // Adiciona dados globais disponíveis em todas as views
        $data['user'] = $this->session->getUser();
        $data['companyId'] = $this->session->getCompanyId();
        $data['tenantId'] = $this->session->getTenantId();
        $data['pageTitle'] = $data['pageTitle'] ?? 'Dashboard';
        $data['activeMenu'] = $data['activeMenu'] ?? '';
        $data['modulosAtivos'] = $data['modulosAtivos'] ?? $this->obterModulosAtivos();

        // Buscar logo da empresa (disponível globalmente em todas as views)
        $data['companyLogo'] = $data['companyLogo'] ?? $this->obterLogoEmpresa();

        // Buscar contas vencidas (disponível globalmente em todas as views)
        $data['contasVencidas'] = $data['contasVencidas'] ?? $this->obterContasVencidas();

        // Buscar permissões do perfil de acesso (disponível globalmente em todas as views)
        $data['permissoesPerfil'] = $data['permissoesPerfil'] ?? $this->obterPermissoesPerfil();

        $flashMessages = [
            'success' => null,
            'error' => null,
            'warning' => null,
        ];

        $flashMap = [
            'success' => 'success_message',
            'error' => 'error_message',
            'warning' => 'warning_message',
        ];

        foreach ($flashMap as $key => $flashKey) {
            if ($this->session->hasFlash($flashKey)) {
                $flashMessages[$key] = $this->session->getFlash($flashKey);
            }
        }

        $data['flashMessages'] = $data['flashMessages'] ?? $flashMessages;

        $this->response->view($view, $data);
    }

    private function obterModulosAtivos(): array
    {
        static $cache = null;

        if ($cache !== null) {
            return $cache;
        }

        $cache = [
            '__controle' => false,
            'lista' => []
        ];

        if (!$this->db) {
            return $cache;
        }

        try {
            $stmt = $this->db->query("SHOW TABLES LIKE 'modulos'");
            if (!$stmt->fetchColumn()) {
                return $cache;
            }

            // Se a tabela existe, ativa o controle de módulos
            // Mesmo que não haja módulos ativos, o controle deve estar ativo
            $cache['__controle'] = true;

            $stmt = $this->db->query("SELECT nome_interface, ativo FROM modulos");
            $ativos = [];
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $nomeInterface = $row['nome_interface'] ?? null;
                $ativo = strtoupper((string) ($row['ativo'] ?? 'NAO'));
                // Aceita 'SIM', '1', 'TRUE', 'S', 'YES' como valores ativos
                if ($nomeInterface && in_array($ativo, ['SIM', '1', 'TRUE', 'S', 'YES'], true)) {
                    $ativos[] = $nomeInterface;
                }
            }

            // Define a lista de módulos ativos (pode estar vazia se nenhum estiver ativo)
            $cache['lista'] = $ativos;
        } catch (\Throwable $e) {
            error_log('[BaseController] Erro ao carregar módulos ativos: ' . $e->getMessage());
        }

        return $cache;
    }

    /**
     * Obtém as permissões do perfil de acesso do usuário logado
     * Retorna array associativo com módulo => ['can_view' => bool, 'can_create' => bool, etc]
     */
    private function obterPermissoesPerfil(): array
    {
        static $cache = null;

        if ($cache !== null) {
            return $cache;
        }

        $cache = [];

        try {
            $user = $this->session->getUser();
            if (!$user) {
                return $cache;
            }

            // Se o usuário é admin, retorna todas as permissões como true
            if (isset($user['role']) && $user['role'] === 'admin') {
                // Para admin, retornar permissões vazias (será tratado na função helper)
                return ['__is_admin' => true];
            }

            // Verificar se o usuário tem perfil_acesso_id
            $perfilAcessoId = $user['perfil_acesso_id'] ?? null;
            if (!$perfilAcessoId) {
                // Se não tem perfil, usuário id = 1 tem acesso total
                if (isset($user['id']) && $user['id'] === 1) {
                    return ['__is_admin' => true];
                }
                return $cache;
            }

            // Verificar se a tabela perfis_acesso_permissoes existe
            if (!$this->db) {
                return $cache;
            }

            $stmt = $this->db->query("SHOW TABLES LIKE 'perfis_acesso_permissoes'");
            if (!$stmt->fetch()) {
                // Se a tabela não existe, usuário id = 1 tem acesso total
                if (isset($user['id']) && $user['id'] === 1) {
                    return ['__is_admin' => true];
                }
                return $cache;
            }

            $companyId = $this->session->getCompanyId();
            if (!$companyId) {
                return $cache;
            }

            // Buscar permissões do perfil
            $stmt = $this->db->prepare("
                SELECT module, can_view, can_create, can_edit, can_delete
                FROM perfis_acesso_permissoes
                WHERE perfil_acesso_id = :perfil_id AND company_id = :company_id
            ");
            $stmt->execute([
                'perfil_id' => $perfilAcessoId,
                'company_id' => $companyId
            ]);

            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $cache[$row['module']] = [
                    'can_view' => (bool) $row['can_view'],
                    'can_create' => (bool) $row['can_create'],
                    'can_edit' => (bool) $row['can_edit'],
                    'can_delete' => (bool) $row['can_delete']
                ];
            }
        } catch (\Exception $e) {
            error_log('[BaseController] Erro ao buscar permissões do perfil: ' . $e->getMessage());
        }

        return $cache;
    }

    /**
     * Obtém a logo da empresa com id = 1
     */
    private function obterLogoEmpresa(): ?string
    {
        static $cache = null;

        if ($cache !== null) {
            return $cache;
        }

        $logo = null;

        if ($this->db) {
            try {
                // Verificar se a tabela empresas existe
                $stmt = $this->db->query("SHOW TABLES LIKE 'empresas'");
                $tabelaExiste = $stmt->fetch() !== false;

                if ($tabelaExiste) {
                    // Busca logo da empresa com id = 1 na tabela empresas
                    $stmt = $this->db->prepare("
                        SELECT logo, razao_social, nome_fantasia
                        FROM empresas
                        WHERE id = 1
                        LIMIT 1
                    ");
                    $stmt->execute();
                    $empresa = $stmt->fetch(\PDO::FETCH_ASSOC);

                    if ($empresa) {
                        // Verificar se logo não é NULL e não está vazio
                        $logoValue = $empresa['logo'] ?? null;

                        if (!empty($logoValue) && trim($logoValue) !== '' && strtolower($logoValue) !== 'null') {
                            $logo = trim($logoValue);
                        }
                    }
                }
            } catch (\Exception $e) {
                // Ignora erro silenciosamente
                error_log("Erro ao buscar logo da empresa no BaseController: " . $e->getMessage());
            }
        }

        $cache = $logo;
        return $logo;
    }

    /**
     * Busca contas a receber vencidas (disponível globalmente)
     */
    private function obterContasVencidas(): array
    {
        $result = [
            'quantidade' => 0,
            'total' => 0,
            'contas' => []
        ];

        if (!$this->db) {
            return $result;
        }

        try {
            $companyId = $this->getCompanyId();
            if (!$companyId) {
                return $result;
            }

            // Verificar se a tabela contas_receber existe
            $stmt = $this->db->query("SHOW TABLES LIKE 'contas_receber'");
            $tabelaExiste = $stmt->fetch() !== false;

            if (!$tabelaExiste) {
                return $result;
            }

            // Verificar estrutura da tabela
            $stmtCols = $this->db->query("SHOW COLUMNS FROM contas_receber");
            $colunas = $stmtCols->fetchAll(\PDO::FETCH_COLUMN, 0);
            $temStatus = in_array('status', $colunas ?? [], true);
            $temStatusId = in_array('status_id', $colunas ?? [], true);

            $dataAtual = date('Y-m-d');

            // Buscar contas vencidas onde status != 'pago'
            $params = [
                'company_id' => $companyId,
                'data_atual' => $dataAtual
            ];

            $sql = "
                SELECT cr.id, cr.description, cr.amount, cr.amount_received, cr.amount_paid, cr.due_date,
                       COALESCE(cr.amount_received, cr.amount_paid, 0) as amount_received_actual,
                       (cr.amount - COALESCE(cr.amount_received, cr.amount_paid, 0)) as amount_remaining
                FROM contas_receber cr
                WHERE cr.company_id = :company_id
                AND cr.due_date < :data_atual
            ";

            // Adicionar filtro de status
            if ($temStatusId) {
                // Buscar ID do status "pago" ou equivalente
                $stmtStatus = $this->db->prepare("
                    SELECT id FROM modulo_status
                    WHERE company_id = :company_id
                    AND (modulo = 'entradas' OR modulo = 'contas_receber')
                    AND (codigo = 'pago' OR nome LIKE '%pago%' OR nome LIKE '%paga%')
                    AND ativo = 1
                    LIMIT 1
                ");
                $stmtStatus->execute(['company_id' => $companyId]);
                $statusPago = $stmtStatus->fetch(\PDO::FETCH_ASSOC);

                if ($statusPago && !empty($statusPago['id'])) {
                    $sql .= " AND (cr.status_id IS NULL OR cr.status_id != :status_pago_id)";
                    $params['status_pago_id'] = $statusPago['id'];
                }
            } elseif ($temStatus) {
                $sql .= " AND (cr.status IS NULL OR cr.status NOT IN ('pago', 'Pago', 'PAGO', 'recebido', 'Recebido'))";
            }

            $sql .= " ORDER BY cr.due_date ASC";

            $stmt = $this->db->prepare($sql);
            $stmt->execute($params);
            $contas = $stmt->fetchAll(\PDO::FETCH_ASSOC);

            // Calcular total das contas vencidas (valor restante a receber)
            $total = 0;
            foreach ($contas as $conta) {
                $valorTotal = (float) ($conta['amount'] ?? 0);
                $valorRecebido = (float) ($conta['amount_received_actual'] ?? 0);
                $valorRestante = $valorTotal - $valorRecebido;

                if ($valorRestante > 0) {
                    $total += $valorRestante;
                }
            }

            $result = [
                'quantidade' => count($contas),
                'total' => $total,
                'contas' => $contas
            ];
        } catch (\Exception $e) {
            error_log("Erro ao buscar contas vencidas no BaseController: " . $e->getMessage());
        }

        return $result;
    }

    /**
     * Retorna JSON de sucesso
     */
    protected function success(string $message, array $data = []): void
    {
        // Se o flag estiver setado, não enviar resposta JSON (usado para processamento automático)
        if (!empty($GLOBALS['__skip_success_response'])) {
            return;
        }
        $this->response->success($message, $data);
    }

    /**
     * Retorna JSON de erro
     */
    protected function error(string $message, array $errors = [], int $statusCode = 400): void
    {
        // Se for erro 401 (não autenticado) e não for requisição AJAX, redirecionar para login
        if ($statusCode === 401 && !$this->request->isAjax()) {
            $this->redirect('/login');
            return;
        }

        // ANTES de retornar erro, verificar se há um boleto Shipay gerado que precisa ser retornado
        // Isso é uma verificação de segurança para garantir que nunca perdemos um boleto gerado
        $chargeIdGlobal = $GLOBALS['__shipay_charge_id'] ?? null;
        error_log("[BaseController::error] 🔍 Verificando se há boleto Shipay gerado. ChargeId global: " . ($chargeIdGlobal ?? 'NULL') . ", Mensagem: " . $message);

        if (!empty($chargeIdGlobal)) {
            error_log("[BaseController::error] ✅ Boleto Shipay encontrado! Retornando sucesso ao invés de erro.");
            $this->success("Cobrança Shipay gerada com sucesso! (Houve um erro ao processar)", [
                'boleto' => [
                    'charge_id' => $chargeIdGlobal,
                    'status' => $GLOBALS['__shipay_status'] ?? 'pending',
                    'linha_digitavel' => $GLOBALS['__shipay_linha_digitavel'] ?? null,
                    'codigo_barras' => $GLOBALS['__shipay_codigo_barras'] ?? null,
                    'qrcode' => $GLOBALS['__shipay_qr_code'] ?? null,
                    'qrcode_text' => $GLOBALS['__shipay_qr_code_text'] ?? null,
                    'pdf_url' => $GLOBALS['__shipay_pdf_url'] ?? null,
                ],
                'tipo' => $GLOBALS['__shipay_tipo'] ?? 'pix',
                'existente' => false,
                'aviso' => 'Boleto gerado, mas houve erro: ' . $message
            ]);
            // Limpar variáveis globais
            unset(
                $GLOBALS['__shipay_charge_id'],
                $GLOBALS['__shipay_status'],
                $GLOBALS['__shipay_linha_digitavel'],
                $GLOBALS['__shipay_codigo_barras'],
                $GLOBALS['__shipay_qr_code'],
                $GLOBALS['__shipay_qr_code_text'],
                $GLOBALS['__shipay_pdf_url'],
                $GLOBALS['__shipay_tipo']
            );
            return;
        }

        error_log("[BaseController::error] ❌ Nenhum boleto Shipay encontrado. Retornando erro normalmente.");
        $this->response->error($message, $errors, $statusCode);
    }

    /**
     * Verifica se o usuário logado tem permissão para uma ação específica em um módulo
     *
     * @param string $module Nome do módulo (ex: 'pessoas', 'produtos', 'vendas')
     * @param string $action Ação a verificar ('view', 'create', 'edit', 'delete')
     * @return bool
     */
    protected function hasPermission(string $module, string $action): bool
    {
        try {
            $userId = $this->getUserId();

            // Se não houver usuário logado, não tem permissão
            if (!$userId) {
                return false;
            }

            // Verificar se o usuário é admin (admins têm todas as permissões)
            $user = $this->getUser();
            if ($user && isset($user['role']) && $user['role'] === 'admin') {
                return true;
            }

            // Verificar se a tabela user_permissions existe
            if (!$this->db) {
                return true; // Se não tem DB, permite por padrão
            }

            $stmt = $this->db->query("SHOW TABLES LIKE 'user_permissions'");
            if (!$stmt->fetch()) {
                // Se a tabela não existe, permite por padrão
                return true;
            }

            // Mapear ação para coluna na tabela
            $actionColumn = 'can_' . $action; // can_view, can_create, can_edit, can_delete

            // Verificar se existe algum registro de permissão para este módulo
            $stmt = $this->db->prepare("
                SELECT {$actionColumn}
                FROM user_permissions
                WHERE user_id = :user_id
                  AND module = :module
                LIMIT 1
            ");
            $stmt->execute([
                'user_id' => $userId,
                'module' => $module
            ]);
            $permission = $stmt->fetch();

            // Se não houver registro de permissão para este módulo
            if (!$permission) {
                // Apenas o usuário id = 1 tem acesso permitido por padrão quando não há registro
                if ($userId === 1) {
                    return true;
                }
                // Para outros usuários, nega acesso se não houver registro
                return false;
            }

            // Se houver registro, verifica se a permissão específica está habilitada
            return (bool) ($permission[$actionColumn] ?? false);
        } catch (\Exception $e) {
            error_log("Erro ao verificar permissão ({$module}/{$action}): " . $e->getMessage());
            // Em caso de erro, retorna false por segurança
            return false;
        }
    }

    /**
     * Verifica se o usuário logado tem permissão para visualizar um módulo
     */
    protected function canView(string $module): bool
    {
        return $this->hasPermission($module, 'view');
    }

    /**
     * Verifica se o usuário logado tem permissão para criar em um módulo
     */
    protected function canCreate(string $module): bool
    {
        return $this->hasPermission($module, 'create');
    }

    /**
     * Verifica se o usuário logado tem permissão para editar em um módulo
     */
    protected function canEdit(string $module): bool
    {
        return $this->hasPermission($module, 'edit');
    }

    /**
     * Verifica se o usuário logado tem permissão para excluir em um módulo
     */
    protected function canDelete(string $module): bool
    {
        return $this->hasPermission($module, 'delete');
    }

    /**
     * Redireciona para uma URL
     */
    protected function redirect(string $url): void
    {
        error_log("==========================================");
        error_log("[BaseController::redirect] Iniciando redirecionamento");
        error_log("[BaseController::redirect] URL recebida: " . $url);

        // Se a URL não começa com http, adiciona o base path
        if (!preg_match('/^https?:\/\//', $url)) {
            $originalUrl = $url;
            $url = UrlHelper::url($url);
            error_log("[BaseController::redirect] URL original (sem base path): " . $originalUrl);
            error_log("[BaseController::redirect] URL com base path: " . $url);
        } else {
            error_log("[BaseController::redirect] URL já completa, usando como está");
        }

        error_log("[BaseController::redirect] URL final que será usada: " . $url);
        error_log("[BaseController::redirect] 🔄 EXECUTANDO REDIRECIONAMENTO...");
        error_log("==========================================");

        $this->response->redirect($url);
    }

    /**
     * Obtém o usuário autenticado
     */
    protected function getUser(): ?array
    {
        return $this->session->getUser();
    }

    /**
     * Obtém o ID do usuário autenticado
     */
    protected function getUserId(): ?int
    {
        $value = $this->session->get('user_id');
        return $value !== null ? (int) $value : null;
    }

    /**
     * Obtém o ID da empresa atual
     */
    protected function getCompanyId(): ?int
    {
        return $this->session->getCompanyId();
    }

    /**
     * Valida dados da requisição
     */
    protected function validate(array $rules): array
    {
        return $this->request->validate($rules);
    }

    /**
     * Registra uma atividade no log
     */
    protected function logActivity(string $action, string $model, ?int $modelId = null, ?array $changes = null): void
    {
        if (!$this->db) {
            return;
        }

        try {
            // Verificar se a tabela existe antes de tentar inserir
            $stmt = $this->db->query("SHOW TABLES LIKE 'activity_logs'");
            if (!$stmt->fetch()) {
                // Tabela não existe, não faz nada (silenciosamente)
                return;
            }

            $stmt = $this->db->prepare("
                INSERT INTO activity_logs (
                    company_id, user_id, action, model, model_id,
                    changes, ip_address, user_agent
                ) VALUES (
                    :company_id, :user_id, :action, :model, :model_id,
                    :changes, :ip_address, :user_agent
                )
            ");

            $stmt->execute([
                'company_id' => $this->getCompanyId(),
                'user_id' => $this->getUserId(),
                'action' => $action,
                'model' => $model,
                'model_id' => $modelId,
                'changes' => $changes ? json_encode($changes) : null,
                'ip_address' => $this->request->ip(),
                'user_agent' => $this->request->userAgent()
            ]);
        } catch (\Exception $e) {
            // Não loga o erro se a tabela não existir (é esperado em alguns ambientes)
            if (
                strpos($e->getMessage(), "doesn't exist") === false &&
                strpos($e->getMessage(), "Base table or view not found") === false
            ) {
                error_log("Erro ao registrar atividade: " . $e->getMessage());
            }
        }
    }

    /**
     * Salva log em arquivo no storage/logs
     */
    protected function logToFile(string $message, string $filename = 'app.log'): void
    {
        try {
            // Usar caminho absoluto baseado no ROOT_PATH ou __DIR__
            $rootPath = defined('ROOT_PATH') ? ROOT_PATH : dirname(__DIR__, 2);
            $logDir = $rootPath . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'logs';

            // Criar diretório se não existir
            if (!is_dir($logDir)) {
                $created = @mkdir($logDir, 0755, true);
                if (!$created && !is_dir($logDir)) {
                    error_log("ERRO: Não foi possível criar diretório de logs: {$logDir}");
                    // Tentar criar no diretório temp como fallback
                    $logDir = $rootPath . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'temp';
                }
            }

            // Verificar se o diretório é gravável
            if (!is_writable($logDir)) {
                error_log("ERRO: Diretório de logs não é gravável: {$logDir}");
                // Tentar usar o diretório temp
                $logDir = $rootPath . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'temp';
                if (!is_writable($logDir)) {
                    error_log("ERRO: Diretório temp também não é gravável: {$logDir}");
                    return;
                }
            }

            $logFile = $logDir . DIRECTORY_SEPARATOR . $filename;
            $timestamp = date('Y-m-d H:i:s');
            $logMessage = "[{$timestamp}] {$message}" . PHP_EOL;

            $result = @file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX);
            if ($result === false) {
                $lastError = error_get_last();
                error_log("ERRO: Não foi possível escrever no arquivo de log: {$logFile}");
                if ($lastError) {
                    error_log("Último erro PHP: " . $lastError['message']);
                }
            }
        } catch (\Exception $e) {
            error_log("ERRO ao salvar log em arquivo: " . $e->getMessage());
            error_log("Stack trace: " . $e->getTraceAsString());
        }
    }

    /**
     * Verifica se pode realizar ação (limites do plano)
     */
    protected function canPerformAction(string $action): bool
    {
        return $this->tenantManager->canPerformAction($action);
    }
}
