<?php

declare(strict_types=1);

namespace PpPhp\Core\Migrate;

use PpPhp\Core\DB;

/**
 * Read-only survey of everything on THIS install that a server migration could
 * move: the platform database, each project, the managed user databases, and the
 * on-disk file sets (uploads, custom files, runtime storage). It computes nothing
 * destructive — it just tells the owner what exists and how big it is, so the
 * Migrate console can present per-item checkboxes with size estimates.
 */
class MigrationInventory
{
    /** Safety cap so sizing a pathological tree can never hang a request. */
    private const MAX_FILES_SCAN = 200000;

    /** The platform's own central database: name, byte size, table count. */
    public static function platformDatabase(): array
    {
        $name = '';
        $bytes = 0;
        $tables = 0;
        try {
            $cfg = require constant('BASE_PATH') . '/config/database.php';
            $conn = $cfg['connections'][$cfg['default']] ?? $cfg;
            $name = (string) ($conn['database'] ?? $conn['dbname'] ?? '');
            DB::Init();
            DB::SetConnection();
            $rows = DB::FetchAll(
                'SELECT COUNT(*) AS t, COALESCE(SUM(data_length + index_length), 0) AS b '
                . 'FROM information_schema.TABLES WHERE table_schema = ?',
                [$name]
            );
            $tables = (int) ($rows[0]['t'] ?? 0);
            $bytes  = (int) ($rows[0]['b'] ?? 0);
        } catch (\Throwable $e) {
            // Non-fatal: show the name we have, size unknown.
        }
        return ['name' => $name, 'bytes' => $bytes, 'tables' => $tables];
    }

    /** Every project folder (owner-slug / project-slug) with its on-disk size. */
    public static function projects(): array
    {
        $out = [];
        $root = constant('BASE_PATH') . '/projects';
        if (!is_dir($root)) {
            return $out;
        }
        foreach (glob($root . '/*', GLOB_ONLYDIR) ?: [] as $ownerDir) {
            $owner = basename($ownerDir);
            foreach (glob($ownerDir . '/*', GLOB_ONLYDIR) ?: [] as $projDir) {
                $slug = basename($projDir);
                $out[] = [
                    'owner' => $owner,
                    'slug'  => $slug,
                    'id'    => $owner . '/' . $slug,
                    'path'  => 'projects/' . $owner . '/' . $slug,
                    'bytes' => self::dirSize($projDir),
                ];
            }
        }
        usort($out, static fn ($a, $b) => $b['bytes'] <=> $a['bytes']);
        return $out;
    }

    /** Managed user databases (owner-provisioned), if the service is available. */
    public static function managedDatabases(): array
    {
        $out = [];
        if (!class_exists(\PpPhp\Core\Databases\ManagedDatabaseService::class)) {
            return $out;
        }
        try {
            foreach ((new \PpPhp\Core\Databases\ManagedDatabaseService())->databases() as $d) {
                $out[] = [
                    'id'       => (int) ($d['id'] ?? 0),
                    'name'     => (string) ($d['name'] ?? $d['db_name'] ?? ''),
                    'server'   => (string) ($d['server_name'] ?? $d['server'] ?? ''),
                    'assigned' => (string) ($d['assigned_username'] ?? $d['assigned_to'] ?? ''),
                    'bytes'    => (int) ($d['size_bytes'] ?? $d['bytes'] ?? 0),
                ];
            }
        } catch (\Throwable $e) {}
        return $out;
    }

    /** On-disk file sets that carry user content (not code — code ships in the bundle). */
    public static function fileSets(): array
    {
        $defs = [
            ['key' => 'uploads',     'label' => 'Uploaded files',        'path' => 'public/uploads'],
            ['key' => 'customfiles', 'label' => 'Custom project files',  'path' => 'projects'],
            ['key' => 'storage',     'label' => 'Runtime storage',       'path' => 'storage'],
            ['key' => 'output',      'label' => 'Generated app builds',  'path' => 'output'],
        ];
        $out = [];
        foreach ($defs as $d) {
            $abs = constant('BASE_PATH') . '/' . $d['path'];
            if (!is_dir($abs)) {
                continue;
            }
            $out[] = $d + ['bytes' => self::dirSize($abs)];
        }
        return $out;
    }

    /** A one-call summary for the console. */
    public static function summary(): array
    {
        return [
            'platform'  => self::platformDatabase(),
            'projects'  => self::projects(),
            'managed'   => self::managedDatabases(),
            'fileSets'  => self::fileSets(),
        ];
    }

    /** Recursive directory size in bytes, hard-capped against pathological trees. */
    public static function dirSize(string $dir): int
    {
        $bytes = 0;
        $seen = 0;
        try {
            $it = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
                \RecursiveIteratorIterator::LEAVES_ONLY
            );
            foreach ($it as $f) {
                if (++$seen > self::MAX_FILES_SCAN) {
                    break;
                }
                if ($f->isFile()) {
                    $bytes += (int) $f->getSize();
                }
            }
        } catch (\Throwable $e) {}
        return $bytes;
    }

    /** Human-readable byte size. */
    public static function human(int $bytes): string
    {
        if ($bytes <= 0) {
            return '0 B';
        }
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = (int) floor(log($bytes, 1024));
        $i = max(0, min($i, count($units) - 1));
        return round($bytes / (1024 ** $i), $i ? 1 : 0) . ' ' . $units[$i];
    }
}
