<?php

namespace App\Services;

use PDO;
use Throwable;

class LegGovMetricsService
{
    public function enrich(array $app): array
    {
        $app['status'] = 'unknown';
        $app['database_status'] = 'not_monitored';
        $app['active_users'] = null;
        $app['database_size_bytes'] = null;
        $app['database_size'] = '—';
        $app['files_size_bytes'] = null;
        $app['files_size'] = '—';
        $app['last_activity'] = null;
        $app['error'] = null;
        $app['monitoring'] = 'partial';
        $app['http_status'] = 'not_monitored';
        $app['http_code'] = null;
        $app['http_url'] = null;

        /*
         * Os sistemas LegGov homologados continuam usando exatamente
         * o monitoramento já existente através do próprio .env.
         *
         * Sistemas externos não têm suas credenciais carregadas pelo
         * Central. Eles entram inicialmente em monitoramento parcial.
         */
        if (in_array(($app['type'] ?? ''), ['laravel', 'php'], true)) {
            try {
                $env = $this->readEnv($app['path'].'/.env');

                $pdo = $this->connect($env);

                if (!$pdo) {
                    $app['status'] = 'offline';
                    $app['database_status'] = 'offline';
                    $app['error'] = 'Banco indisponível';
                } else {
                    $app['database_status'] = 'online';

                    $database = $app['database'] ?? null;

                    if ($database) {
                        $bytes = $this->databaseSize($pdo, $database);

                        $app['database_size_bytes'] = $bytes;
                        $app['database_size'] = $this->humanBytes($bytes);
                    }

                    if (($app['type'] ?? '') === 'laravel') {
                        $activity = $this->laravelActivity($pdo);
                    } else {
                        $activity = $this->phpActivity($pdo);
                    }

                    $app['active_users'] = $activity['active_users'];
                    $app['last_activity'] = $activity['last_activity'];
                    $app['status'] = 'online';
                    $app['monitoring'] = 'full';
                }
            } catch (Throwable $e) {
                $app['status'] = 'offline';
                $app['database_status'] = 'offline';
                $app['error'] = 'Falha ao consultar métricas';
            }
        }

        /*
         * Sistemas externos:
         * consulta somente a view database_metrics do próprio banco Central.
         * Nenhuma credencial dos sistemas externos é utilizada.
         */
        if (
            ($app['type'] ?? '') === 'php_external'
            && !empty($app['database'])
        ) {
            try {
                $metric = \Illuminate\Support\Facades\DB::table('database_metrics')
                    ->where('database_name', $app['database'])
                    ->first();

                if ($metric) {
                    $bytes = (int) $metric->size_bytes;

                    $app['database_status'] = 'online';
                    $app['database_size_bytes'] = $bytes;
                    $app['database_size'] = $this->humanBytes($bytes);
                }
            } catch (Throwable $e) {
                /*
                 * Mantém not_monitored em caso de falha.
                 * Não transforma falha da telemetria em indisponibilidade
                 * do sistema monitorado.
                 */
            }
        }

        $filesBytes = $this->directorySize($app['path']);

        if ($filesBytes !== null) {
            $app['files_size_bytes'] = $filesBytes;
            $app['files_size'] = $this->humanBytes($filesBytes);

            /*
             * Para sistemas externos, a existência/leitura da instalação
             * confirma que ela foi localizada, mas não equivale a afirmar
             * que aplicação HTTP ou banco estão online.
             */
            if (($app['type'] ?? '') === 'php_external') {
                $app['status'] = 'detected';
            }
        }

        /*
         * Health check HTTP dos sistemas externos.
         * Só é executado quando o Discovery encontrou domínio público.
         */
        if (($app['type'] ?? '') === 'php_external') {
            $domains = $app['domains'] ?? [];

            if (!empty($domains)) {
                $health = $this->httpHealth($domains);

                $app['http_status'] = $health['status'];
                $app['http_code'] = $health['code'];
                $app['http_url'] = $health['url'];

                if ($health['status'] === 'online') {
                    $app['status'] = 'online';
                } elseif ($health['status'] === 'offline') {
                    $app['status'] = 'offline';
                }
            }
        }

        return $app;
    }

    private function httpHealth(array $domains): array
    {
        foreach ($domains as $domain) {
            $domain = trim((string) $domain);

            if ($domain === '') {
                continue;
            }

            $url = str_starts_with($domain, 'http://')
                || str_starts_with($domain, 'https://')
                    ? $domain
                    : 'https://'.$domain;

            $ch = curl_init($url);

            if ($ch === false) {
                continue;
            }

            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_NOBODY => true,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_CONNECTTIMEOUT => 3,
                CURLOPT_TIMEOUT => 8,
                CURLOPT_MAXREDIRS => 5,
                CURLOPT_USERAGENT => 'LegGov-Central/1.0',
            ]);

            curl_exec($ch);

            $code = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
            $finalUrl = (string) curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
            $error = curl_errno($ch);

            curl_close($ch);

            if ($error === 0 && $code >= 200 && $code < 500) {
                return [
                    'status' => 'online',
                    'code' => $code,
                    'url' => $finalUrl ?: $url,
                ];
            }

            if ($error === 0 && $code >= 500) {
                return [
                    'status' => 'offline',
                    'code' => $code,
                    'url' => $finalUrl ?: $url,
                ];
            }
        }

        return [
            'status' => 'offline',
            'code' => null,
            'url' => null,
        ];
    }

    private function laravelActivity(PDO $pdo): array
    {
        try {
            $limit = time() - 300;

            $stmt = $pdo->prepare("
                SELECT
                    COUNT(DISTINCT user_id) AS ativos,
                    MAX(last_activity) AS ultima
                FROM sessions
                WHERE user_id IS NOT NULL
                  AND last_activity >= ?
            ");

            $stmt->execute([$limit]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];

            return [
                'active_users' => (int) ($row['ativos'] ?? 0),
                'last_activity' => !empty($row['ultima'])
                    ? date('Y-m-d H:i:s', (int) $row['ultima'])
                    : null,
            ];
        } catch (Throwable) {
            return [
                'active_users' => null,
                'last_activity' => null,
            ];
        }
    }

    private function phpActivity(PDO $pdo): array
    {
        try {
            $stmt = $pdo->query("
                SELECT
                    COUNT(DISTINCT usuario_id) AS ativos,
                    MAX(ultima_atividade) AS ultima
                FROM sessoes_ativas
                WHERE ultima_atividade >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
            ");

            $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];

            return [
                'active_users' => (int) ($row['ativos'] ?? 0),
                'last_activity' => $row['ultima'] ?? null,
            ];
        } catch (Throwable) {
            return [
                'active_users' => null,
                'last_activity' => null,
            ];
        }
    }

    private function databaseSize(PDO $pdo, string $database): int
    {
        $stmt = $pdo->prepare("
            SELECT COALESCE(SUM(data_length + index_length), 0)
            FROM information_schema.tables
            WHERE table_schema = ?
        ");

        $stmt->execute([$database]);

        return (int) $stmt->fetchColumn();
    }

    private function connect(array $env): ?PDO
    {
        $host = $env['DB_HOST'] ?? '127.0.0.1';
        $port = $env['DB_PORT'] ?? '3306';

        $database = $env['DB_DATABASE']
            ?? $env['DB_NAME']
            ?? $env['DATABASE_NAME']
            ?? null;

        $username = $env['DB_USERNAME']
            ?? $env['DB_USER']
            ?? null;

        $password = $env['DB_PASSWORD']
            ?? $env['DB_PASS']
            ?? '';

        if (!$database || !$username) {
            return null;
        }

        return new PDO(
            "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4",
            $username,
            $password,
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_TIMEOUT => 2,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]
        );
    }

    private function directorySize(string $path): ?int
    {
        if (!is_dir($path)) {
            return null;
        }

        $output = [];
        $code = 0;

        exec(
            'du -sb '.escapeshellarg($path).' 2>/dev/null',
            $output,
            $code
        );

        if ($code !== 0 || empty($output)) {
            return null;
        }

        $parts = preg_split('/\s+/', trim($output[0]));

        return isset($parts[0]) && is_numeric($parts[0])
            ? (int) $parts[0]
            : null;
    }

    private function humanBytes(?int $bytes): string
    {
        if ($bytes === null) {
            return '—';
        }

        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $size = (float) $bytes;
        $unit = 0;

        while ($size >= 1024 && $unit < count($units) - 1) {
            $size /= 1024;
            $unit++;
        }

        return number_format(
            $size,
            $unit === 0 ? 0 : 1,
            ',',
            '.'
        ).' '.$units[$unit];
    }

    private function readEnv(string $file): array
    {
        $result = [];

        foreach (
            @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []
            as $line
        ) {
            $line = trim($line);

            if (
                $line === '' ||
                str_starts_with($line, '#') ||
                !str_contains($line, '=')
            ) {
                continue;
            }

            [$key, $value] = explode('=', $line, 2);

            $result[trim($key)] = trim(trim($value), "\"'");
        }

        return $result;
    }
}
