<?php

declare(strict_types=1);

namespace App\Framework\Process\Services;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Process\Process;
use App\Framework\Process\ValueObjects\Command;
use App\Framework\Process\ValueObjects\Docker\DockerContainer;
use App\Framework\Process\ValueObjects\Docker\DockerContainerStats;

/**
 * Docker Service.
 *
 * Verwaltet Docker-Container-Operationen wie Listing, Stats, Logs und Container-Management.
 */
final readonly class DockerService
{
    public function __construct(
        private Process $process
    ) {
    }

    /**
     * Listet alle Container auf (laufend und gestoppt).
     *
     * @return DockerContainer[]
     */
    public function listContainers(bool $all = false): array
    {
        $args = ['docker', 'ps', '--format', '{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}'];

        if ($all) {
            $args[] = '--all';
        }

        $result = $this->process->run(
            command: Command::fromArray($args),
            timeout: Duration::fromSeconds(10)
        );

        if (! $result->isSuccess()) {
            return [];
        }

        $containers = [];
        $lines = explode("\n", trim($result->stdout));

        foreach ($lines as $line) {
            if (empty($line)) {
                continue;
            }

            $parts = explode('|', $line);

            if (count($parts) < 4) {
                continue;
            }

            $containers[] = DockerContainer::fromDockerPs(
                id: $parts[0],
                name: $parts[1],
                image: $parts[2],
                status: $parts[3]
            );
        }

        return $containers;
    }

    /**
     * Ruft Container-Statistiken ab.
     */
    public function getContainerStats(string $containerId): ?DockerContainerStats
    {
        $result = $this->process->run(
            command: Command::fromArray([
                'docker',
                'stats',
                $containerId,
                '--no-stream',
                '--format',
                '{{.CPUPerc}}|{{.MemUsage}}|{{.NetIO}}|{{.BlockIO}}',
            ]),
            timeout: Duration::fromSeconds(5)
        );

        if (! $result->isSuccess()) {
            return null;
        }

        $line = trim($result->stdout);
        $parts = explode('|', $line);

        if (count($parts) < 4) {
            return null;
        }

        // Parse CPU percentage (z.B. "0.50%")
        $cpuPercent = (float) rtrim($parts[0], '%');

        // Parse Memory Usage (z.B. "256MiB / 2GiB")
        $memoryParts = explode(' / ', $parts[1]);
        $memoryUsage = $this->parseBytes($memoryParts[0] ?? '0B');
        $memoryLimit = $this->parseBytes($memoryParts[1] ?? '0B');

        // Parse Network I/O (z.B. "1.2kB / 3.4kB")
        $networkParts = explode(' / ', $parts[2]);
        $networkIn = $this->parseBytes($networkParts[0] ?? '0B');
        $networkOut = $this->parseBytes($networkParts[1] ?? '0B');

        // Parse Block I/O (z.B. "5MB / 10MB")
        $blockParts = explode(' / ', $parts[3]);
        $blockIn = $this->parseBytes($blockParts[0] ?? '0B');
        $blockOut = $this->parseBytes($blockParts[1] ?? '0B');

        // Get container name
        $container = $this->getContainer($containerId);
        $containerName = $container?->name ?? $containerId;

        return DockerContainerStats::fromDockerStats(
            containerId: $containerId,
            containerName: $containerName,
            cpuPercent: $cpuPercent,
            memoryUsageBytes: $memoryUsage,
            memoryLimitBytes: $memoryLimit,
            networkInBytes: $networkIn,
            networkOutBytes: $networkOut,
            blockInBytes: $blockIn,
            blockOutBytes: $blockOut
        );
    }

    /**
     * Ruft detaillierte Container-Informationen ab.
     */
    public function inspectContainer(string $containerId): ?array
    {
        $result = $this->process->run(
            command: Command::fromArray(['docker', 'inspect', $containerId]),
            timeout: Duration::fromSeconds(5)
        );

        if (! $result->isSuccess()) {
            return null;
        }

        $data = json_decode($result->stdout, true);

        return $data[0] ?? null;
    }

    /**
     * Ruft Container-Logs ab.
     */
    public function getContainerLogs(string $containerId, int $lines = 100): ?string
    {
        $result = $this->process->run(
            command: Command::fromArray([
                'docker',
                'logs',
                '--tail',
                (string) $lines,
                $containerId,
            ]),
            timeout: Duration::fromSeconds(10)
        );

        if (! $result->isSuccess()) {
            return null;
        }

        return $result->stdout . $result->stderr;
    }

    /**
     * Startet einen Container.
     */
    public function startContainer(string $containerId): bool
    {
        $result = $this->process->run(
            command: Command::fromArray(['docker', 'start', $containerId]),
            timeout: Duration::fromSeconds(30)
        );

        return $result->isSuccess();
    }

    /**
     * Stoppt einen Container.
     */
    public function stopContainer(string $containerId, int $timeoutSeconds = 10): bool
    {
        $result = $this->process->run(
            command: Command::fromArray([
                'docker',
                'stop',
                '--time',
                (string) $timeoutSeconds,
                $containerId,
            ]),
            timeout: Duration::fromSeconds($timeoutSeconds + 5)
        );

        return $result->isSuccess();
    }

    /**
     * Startet Container neu.
     */
    public function restartContainer(string $containerId, int $timeoutSeconds = 10): bool
    {
        $result = $this->process->run(
            command: Command::fromArray([
                'docker',
                'restart',
                '--time',
                (string) $timeoutSeconds,
                $containerId,
            ]),
            timeout: Duration::fromSeconds($timeoutSeconds + 5)
        );

        return $result->isSuccess();
    }

    /**
     * Entfernt einen Container.
     */
    public function removeContainer(string $containerId, bool $force = false): bool
    {
        $args = ['docker', 'rm'];

        if ($force) {
            $args[] = '--force';
        }

        $args[] = $containerId;

        $result = $this->process->run(
            command: Command::fromArray($args),
            timeout: Duration::fromSeconds(30)
        );

        return $result->isSuccess();
    }

    /**
     * Führt einen Befehl in einem Container aus.
     */
    public function execInContainer(string $containerId, array $command, bool $interactive = false): ?string
    {
        $args = ['docker', 'exec'];

        if ($interactive) {
            $args[] = '-i';
        }

        $args[] = $containerId;
        $args = [...$args, ...$command];

        $result = $this->process->run(
            command: Command::fromArray($args),
            timeout: Duration::fromSeconds(60)
        );

        if (! $result->isSuccess()) {
            return null;
        }

        return $result->stdout;
    }

    /**
     * Ruft Docker-Version ab.
     */
    public function getVersion(): ?string
    {
        $result = $this->process->run(
            command: Command::fromArray(['docker', '--version']),
            timeout: Duration::fromSeconds(5)
        );

        if (! $result->isSuccess()) {
            return null;
        }

        // Parse "Docker version 20.10.17, build 100c701"
        if (preg_match('/Docker version ([^,]+)/', $result->stdout, $matches)) {
            return $matches[1];
        }

        return trim($result->stdout);
    }

    /**
     * Prüft ob Docker läuft.
     */
    public function isRunning(): bool
    {
        $result = $this->process->run(
            command: Command::fromArray(['docker', 'ps']),
            timeout: Duration::fromSeconds(5)
        );

        return $result->isSuccess();
    }

    /**
     * Holt einen einzelnen Container.
     */
    private function getContainer(string $containerId): ?DockerContainer
    {
        $containers = $this->listContainers(all: true);

        foreach ($containers as $container) {
            if ($container->id === $containerId || $container->name === $containerId) {
                return $container;
            }
        }

        return null;
    }

    /**
     * Parst Byte-Strings (z.B. "256MiB", "2GiB", "1.2kB").
     */
    private function parseBytes(string $value): int
    {
        $value = trim($value);

        if (empty($value) || $value === '0B') {
            return 0;
        }

        // Match number and unit
        if (! preg_match('/^([\d.]+)\s*([KMGT]i?B)$/i', $value, $matches)) {
            return 0;
        }

        $number = (float) $matches[1];
        $unit = strtoupper($matches[2]);

        // Binary units (KiB, MiB, GiB, TiB)
        $multipliers = [
            'B' => 1,
            'KB' => 1000,
            'KIB' => 1024,
            'MB' => 1000 * 1000,
            'MIB' => 1024 * 1024,
            'GB' => 1000 * 1000 * 1000,
            'GIB' => 1024 * 1024 * 1024,
            'TB' => 1000 * 1000 * 1000 * 1000,
            'TIB' => 1024 * 1024 * 1024 * 1024,
        ];

        $multiplier = $multipliers[$unit] ?? 1;

        return (int) round($number * $multiplier);
    }
}
