<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects\Services;

use InvalidArgumentException;

/**
 * Service für das Parsen von Versions-Strings
 * Unterstützt SemVer 2.0.0 Format: major.minor.patch[-prerelease][+buildmetadata]
 */
final class VersionParser
{
    private const string VERSION_PATTERN = '/^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9\-\.]+))?(?:\+([a-zA-Z0-9\-\.]+))?$/';
    private const string SIMPLE_VERSION_PATTERN = '/^(\d+)\.(\d+)(?:\.(\d+))?(?:-([a-zA-Z0-9\-\.]+))?(?:\+([a-zA-Z0-9\-\.]+))?$/';

    /**
     * Parst einen Versions-String in seine Komponenten
     */
    public function parse(string $version): array
    {
        if (empty($version)) {
            throw new InvalidArgumentException('Version cannot be empty');
        }

        // Versuche vollständiges SemVer Format
        if (preg_match(self::VERSION_PATTERN, $version, $matches)) {
            return $this->buildParseResult($matches, true);
        }

        // Versuche vereinfachtes Format (major.minor optional .patch)
        if (preg_match(self::SIMPLE_VERSION_PATTERN, $version, $matches)) {
            return $this->buildParseResult($matches, false);
        }

        throw new InvalidArgumentException(
            "Invalid version format: $version. Expected format: major.minor[.patch][-prerelease][+buildmetadata]"
        );
    }

    /**
     * Prüft ob ein Versions-String gültig ist
     */
    public function isValid(string $version): bool
    {
        try {
            $this->parse($version);

            return true;
        } catch (InvalidArgumentException) {
            return false;
        }
    }

    /**
     * Prüft ob ein String dem vollständigen SemVer Format entspricht
     */
    public function isFullSemVer(string $version): bool
    {
        return preg_match(self::VERSION_PATTERN, $version) === 1;
    }

    /**
     * Prüft ob ein String dem vereinfachten Format entspricht
     */
    public function isSimpleFormat(string $version): bool
    {
        return preg_match(self::SIMPLE_VERSION_PATTERN, $version) === 1;
    }

    /**
     * Validiert Pre-Release Format
     */
    public function isValidPreRelease(string $preRelease): bool
    {
        return preg_match('/^[a-zA-Z0-9\-\.]+$/', $preRelease) === 1;
    }

    /**
     * Validiert Build-Metadaten Format
     */
    public function isValidBuildMetadata(string $buildMetadata): bool
    {
        return preg_match('/^[a-zA-Z0-9\-\.]+$/', $buildMetadata) === 1;
    }

    /**
     * Normalisiert eine Version zu vollständigem SemVer Format
     */
    public function normalize(string $version): string
    {
        $parts = $this->parse($version);

        $normalized = "{$parts['major']}.{$parts['minor']}.{$parts['patch']}";

        if ($parts['preRelease'] !== null) {
            $normalized .= "-{$parts['preRelease']}";
        }

        if ($parts['buildMetadata'] !== null) {
            $normalized .= "+{$parts['buildMetadata']}";
        }

        return $normalized;
    }

    /**
     * Baut das Parse-Ergebnis aus den Regex-Matches
     */
    private function buildParseResult(array $matches, bool $isFullSemVer): array
    {
        $result = [
            'major' => (int) $matches[1],
            'minor' => (int) $matches[2],
            'patch' => 0,
            'preRelease' => null,
            'buildMetadata' => null,
        ];

        if ($isFullSemVer) {
            // Vollständiges SemVer: major.minor.patch[-prerelease][+buildmetadata]
            $result['patch'] = (int) $matches[3];
            $result['preRelease'] = isset($matches[4]) && $matches[4] !== '' ? $matches[4] : null;
            $result['buildMetadata'] = isset($matches[5]) && $matches[5] !== '' ? $matches[5] : null;
        } else {
            // Vereinfachtes Format: major.minor[.patch][-prerelease][+buildmetadata]
            $result['patch'] = isset($matches[3]) && $matches[3] !== '' ? (int) $matches[3] : 0;
            $result['preRelease'] = isset($matches[4]) && $matches[4] !== '' ? $matches[4] : null;
            $result['buildMetadata'] = isset($matches[5]) && $matches[5] !== '' ? $matches[5] : null;
        }

        // Validiere Pre-Release und Build-Metadaten
        if ($result['preRelease'] !== null && ! $this->isValidPreRelease($result['preRelease'])) {
            throw new InvalidArgumentException("Invalid pre-release format: {$result['preRelease']}");
        }

        if ($result['buildMetadata'] !== null && ! $this->isValidBuildMetadata($result['buildMetadata'])) {
            throw new InvalidArgumentException("Invalid build metadata format: {$result['buildMetadata']}");
        }

        return $result;
    }
}
