<?php

declare(strict_types=1);

namespace App\Core;

use PDO;
use Exception;

/**
 * Gerenciador de Multi-Tenancy
 * Gerencia múltiplos bancos de dados (um por tenant)
 */
class TenantManager
{
    private ?PDO $masterConnection = null;
    private ?PDO $tenantConnection = null;
    private ?array $currentTenant = null;
    private array $dbConfig;

    public function __construct()
    {
        $this->dbConfig = require \ROOT_PATH . '/config/database.php';
    }

    /**
     * Conecta ao banco master
     */
    public function getMasterConnection(): PDO
    {
        if ($this->masterConnection === null) {
            $config = $this->dbConfig['master'];

            $dsn = sprintf(
                "%s:host=%s;port=%d;dbname=%s;charset=%s",
                $config['driver'],
                $config['host'],
                $config['port'],
                $config['database'],
                $config['charset']
            );

            $this->masterConnection = new PDO(
                $dsn,
                $config['username'],
                $config['password'],
                $config['options']
            );
        }

        return $this->masterConnection;
    }

    /**
     * Identifica o tenant baseado no domínio/subdomínio
     */
    public function identifyTenant(Request $request): ?array
    {
        $host = $request->host();

        // Extrai o subdomínio
        $subdomain = $this->extractSubdomain($host);

        if ($subdomain) {
            $tenant = $this->getTenantBySubdomain($subdomain);

            // Se não existe, cria automaticamente
            if (!$tenant) {
                $tenant = $this->createTenantOnTheFly($subdomain);
            }

            return $tenant;
        }

        // Se não há subdomínio, verifica se é uma rota de registro/login global
        return null;
    }

    /**
     * Extrai o subdomínio do host
     */
    private function extractSubdomain(string $host): ?string
    {
        // Remove porta se houver
        $host = explode(':', $host)[0];
        $host = strtolower($host); // Normaliza para minúsculas

        // Define o domínio base (você pode configurar isso no .env)
        $baseDomain = strtolower($_ENV['BASE_DOMAIN'] ?? 'localhost');

        // Se for exatamente o domínio base ou www.dominio, não há subdomínio
        if ($host === $baseDomain || $host === 'www.' . $baseDomain) {
            return null;
        }

        // Para localhost: formato é [subdominio].localhost
        if (strpos($host, '.localhost') !== false) {
            $parts = explode('.localhost', $host);
            $subdomain = $parts[0] ?? '';
            return $subdomain !== '' ? $subdomain : null;
        }

        // Para domínio web: verifica se termina com o domínio base
        if (strpos($host, '.' . $baseDomain) !== false) {
            // Remove o domínio base do final
            $subdomain = str_replace('.' . $baseDomain, '', $host);

            // Se sobrou apenas o subdomínio
            if ($subdomain && $subdomain !== 'www' && $subdomain !== $host) {
                return $subdomain;
            }
        }

        // Fallback: extrai pela estrutura padrão de domínio
        // Se tiver mais de 2 partes (ex: cliente.Systhema.com.br)
        $parts = explode('.', $host);
        if (count($parts) >= 3) {
            // Ignora se for www
            if ($parts[0] === 'www') {
                return null;
            }
            return $parts[0];
        }

        return null;
    }

    /**
     * Busca tenant pelo subdomínio
     */
    private function getTenantBySubdomain(string $subdomain): ?array
    {
        try {
            $db = $this->getMasterConnection();

            $stmt = $db->prepare("
                SELECT * FROM tenants
                WHERE subdomain = :subdomain
                AND status = 'active'
                LIMIT 1
            ");

            $stmt->execute(['subdomain' => $subdomain]);
            $tenant = $stmt->fetch();

            return $tenant ?: null;

        } catch (Exception $e) {
            error_log("Erro ao buscar tenant: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Cria tenant automaticamente quando não existe
     */
    private function createTenantOnTheFly(string $subdomain): ?array
    {
        try {
            $db = $this->getMasterConnection();
            $databaseName = 'Systhema_tenant_' . $subdomain;

            // Insere tenant
            $stmt = $db->prepare("
                INSERT INTO tenants (name, subdomain, database_name, plan, status, created_at, updated_at)
                VALUES (:name, :subdomain, :database, 'free', 'active', NOW(), NOW())
            ");

            $stmt->execute([
                'name' => ucfirst($subdomain) . ' - Auto',
                'subdomain' => $subdomain,
                'database' => $databaseName
            ]);

            // Cria o banco do tenant
            $db->exec("CREATE DATABASE IF NOT EXISTS `{$databaseName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");

            // Log de sucesso
            error_log("Tenant criado automaticamente: {$subdomain} -> {$databaseName}");

            // Busca o tenant criado
            return $this->getTenantBySubdomain($subdomain);

        } catch (Exception $e) {
            error_log("Erro ao criar tenant on-the-fly: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Define o tenant atual
     */
    public function setCurrentTenant(array $tenant): void
    {
        $this->currentTenant = $tenant;

        // Conecta ao banco do tenant
        $this->connectToTenantDatabase($tenant['database_name']);
    }

    /**
     * Conecta ao banco de dados do tenant
     */
    private function connectToTenantDatabase(string $databaseName): void
    {
        $config = $this->dbConfig['tenant'];

        $dsn = sprintf(
            "%s:host=%s;port=%d;dbname=%s;charset=%s",
            $config['driver'],
            $config['host'],
            $config['port'],
            $databaseName,
            $config['charset']
        );

        $this->tenantConnection = new PDO(
            $dsn,
            $config['username'],
            $config['password'],
            $config['options']
        );
    }

    /**
     * Retorna a conexão com o banco do tenant
     */
    public function getTenantConnection(): ?PDO
    {
        return $this->tenantConnection;
    }

    /**
     * Retorna o tenant atual
     */
    public function getCurrentTenant(): ?array
    {
        return $this->currentTenant;
    }

    /**
     * Cria um novo tenant (banco de dados)
     */
    public function createTenant(array $data): array
    {
        $db = $this->getMasterConnection();

        try {
            $db->beginTransaction();

            // Gera nome do banco de dados
            $databaseName = $this->dbConfig['tenant']['prefix'] . $data['subdomain'];

            // Cria registro do tenant
            $stmt = $db->prepare("
                INSERT INTO tenants (
                    name, subdomain, database_name, plan, status, created_at
                ) VALUES (
                    :name, :subdomain, :database_name, :plan, 'active', NOW()
                )
            ");

            $stmt->execute([
                'name' => $data['name'],
                'subdomain' => $data['subdomain'],
                'database_name' => $databaseName,
                'plan' => $data['plan'] ?? 'free',
            ]);

            $tenantId = $db->lastInsertId();

            // Cria o banco de dados do tenant
            $this->createTenantDatabase($databaseName);

            $db->commit();

            return [
                'id' => $tenantId,
                'name' => $data['name'],
                'subdomain' => $data['subdomain'],
                'database_name' => $databaseName,
                'plan' => $data['plan'] ?? 'free',
            ];

        } catch (Exception $e) {
            $db->rollBack();
            throw new Exception("Erro ao criar tenant: " . $e->getMessage());
        }
    }

    /**
     * Cria o banco de dados do tenant e suas tabelas
     */
    private function createTenantDatabase(string $databaseName): void
    {
        $config = $this->dbConfig['tenant'];

        // Conexão sem banco específico
        $dsn = sprintf(
            "%s:host=%s;port=%d;charset=%s",
            $config['driver'],
            $config['host'],
            $config['port'],
            $config['charset']
        );

        $pdo = new PDO($dsn, $config['username'], $config['password']);

        // Cria o banco de dados
        $pdo->exec("CREATE DATABASE IF NOT EXISTS `{$databaseName}`
                    CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");

        // Conecta ao novo banco
        $pdo->exec("USE `{$databaseName}`");

        // Executa migrations/schema
        $this->runTenantMigrations($pdo);
    }

    /**
     * Executa as migrations do tenant
     */
    private function runTenantMigrations(PDO $pdo): void
    {
        $schemaFile = \ROOT_PATH . '/database/tenant_schema.sql';

        if (file_exists($schemaFile)) {
            $sql = file_get_contents($schemaFile);
            $pdo->exec($sql);
        }
    }

    /**
     * Verifica se o tenant pode realizar uma ação (limites do plano)
     */
    public function canPerformAction(string $action): bool
    {
        if (!$this->currentTenant) {
            return false;
        }

        $planLimits = $this->getPlanLimits($this->currentTenant['plan']);

        // Implementar verificações específicas por tipo de ação
        switch ($action) {
            case 'create_user':
                return $this->checkUserLimit($planLimits);
            case 'create_invoice':
                return $this->checkInvoiceLimit($planLimits);
            default:
                return true;
        }
    }

    /**
     * Obtém os limites do plano
     */
    private function getPlanLimits(string $plan): array
    {
        $appConfig = require \ROOT_PATH . '/config/app.php';
        return $appConfig['plans'][$plan] ?? [];
    }

    /**
     * Verifica limite de usuários
     */
    private function checkUserLimit(array $limits): bool
    {
        if ($limits['users'] === -1) {
            return true; // ilimitado
        }

        $db = $this->getTenantConnection();
        $stmt = $db->query("SELECT COUNT(*) as count FROM users");
        $result = $stmt->fetch();

        return $result['count'] < $limits['users'];
    }

    /**
     * Verifica limite de notas fiscais
     */
    private function checkInvoiceLimit(array $limits): bool
    {
        if ($limits['invoices_per_month'] === -1) {
            return true; // ilimitado
        }

        $db = $this->getTenantConnection();
        $stmt = $db->query("
            SELECT COUNT(*) as count
            FROM invoices
            WHERE MONTH(created_at) = MONTH(CURRENT_DATE())
            AND YEAR(created_at) = YEAR(CURRENT_DATE())
        ");
        $result = $stmt->fetch();

        return $result['count'] < $limits['invoices_per_month'];
    }
}

