<?php

declare(strict_types=1);

namespace App\Framework\Process\Services;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Process\Process;
use App\Framework\Process\ValueObjects\Command;
use App\Framework\Process\ValueObjects\Npm\NpmAuditResult;
use App\Framework\Process\ValueObjects\Npm\NpmPackage;

/**
 * NPM Service.
 *
 * Verwaltet NPM-Operationen wie Installations, Updates, Security Audits und Package-Informationen.
 */
final readonly class NpmService
{
    public function __construct(
        private Process $process,
        private ?FilePath $projectRoot = null
    ) {
    }

    /**
     * Gibt die NPM-Version zurück.
     */
    public function getVersion(): string
    {
        $result = $this->process->run(
            Command::fromArray(['npm', '--version'])
        );

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

        return trim($result->stdout);
    }

    /**
     * Installiert Dependencies.
     */
    public function install(bool $production = false): bool
    {
        $args = ['npm', 'install'];

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

        $result = $this->process->run(
            command: Command::fromArray($args),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromMinutes(5)
        );

        return $result->isSuccess();
    }

    /**
     * Aktualisiert Dependencies.
     */
    public function update(bool $production = false): bool
    {
        $args = ['npm', 'update'];

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

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

        return $result->isSuccess();
    }

    /**
     * Führt Security Audit durch.
     */
    public function audit(): NpmAuditResult
    {
        $result = $this->process->run(
            command: Command::fromArray(['npm', 'audit', '--json']),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromSeconds(60)
        );

        if (! $result->isSuccess()) {
            // Auch bei Vulnerabilities ist Exit-Code != 0
            // Versuche trotzdem JSON zu parsen
        }

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

        if (! is_array($data)) {
            return NpmAuditResult::safe();
        }

        return NpmAuditResult::fromAuditJson($data);
    }

    /**
     * Versucht automatisches Fixing von Vulnerabilities.
     */
    public function auditFix(bool $force = false): bool
    {
        $args = ['npm', 'audit', 'fix'];

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

        $result = $this->process->run(
            command: Command::fromArray($args),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromMinutes(5)
        );

        return $result->isSuccess();
    }

    /**
     * Listet veraltete Packages auf.
     *
     * @return NpmPackage[]
     */
    public function getOutdatedPackages(): array
    {
        $result = $this->process->run(
            command: Command::fromArray(['npm', 'outdated', '--json']),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromSeconds(60)
        );

        // npm outdated gibt Exit-Code != 0 wenn es outdated packages gibt
        $data = json_decode($result->stdout, true);

        if (! is_array($data)) {
            return [];
        }

        $packages = [];

        foreach ($data as $name => $info) {
            $packages[] = NpmPackage::fromNpmOutput(
                name: $name,
                currentVersion: $info['current'] ?? 'unknown',
                wantedVersion: $info['wanted'] ?? null,
                latestVersion: $info['latest'] ?? null,
                type: $info['type'] ?? null
            );
        }

        return $packages;
    }

    /**
     * Listet installierte Packages auf.
     *
     * @return NpmPackage[]
     */
    public function getInstalledPackages(): array
    {
        $result = $this->process->run(
            command: Command::fromArray(['npm', 'list', '--json', '--depth=0']),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromSeconds(30)
        );

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

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

        if (! is_array($data)) {
            return [];
        }

        $packages = [];

        // Dependencies
        foreach ($data['dependencies'] ?? [] as $name => $info) {
            $packages[] = new NpmPackage(
                name: $name,
                currentVersion: $info['version'] ?? 'unknown',
                type: 'dependencies'
            );
        }

        // DevDependencies (wenn --depth=0 nicht nur dependencies zurückgibt)
        foreach ($data['devDependencies'] ?? [] as $name => $info) {
            $packages[] = new NpmPackage(
                name: $name,
                currentVersion: $info['version'] ?? 'unknown',
                type: 'devDependencies'
            );
        }

        return $packages;
    }

    /**
     * Führt NPM Script aus.
     */
    public function runScript(string $script): bool
    {
        $result = $this->process->run(
            command: Command::fromArray(['npm', 'run', $script]),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromMinutes(10)
        );

        return $result->isSuccess();
    }

    /**
     * Cleaned node_modules und package-lock.json.
     */
    public function clean(): bool
    {
        if ($this->projectRoot === null) {
            return false;
        }

        $nodeModules = $this->projectRoot->toString() . '/node_modules';
        $packageLock = $this->projectRoot->toString() . '/package-lock.json';

        // Remove node_modules
        if (is_dir($nodeModules)) {
            $result = $this->process->run(
                command: Command::fromArray(['rm', '-rf', $nodeModules]),
                timeout: Duration::fromMinutes(2)
            );

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

        // Remove package-lock.json
        if (file_exists($packageLock)) {
            $result = $this->process->run(
                command: Command::fromArray(['rm', $packageLock]),
                timeout: Duration::fromSeconds(5)
            );

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

        return true;
    }

    /**
     * Prüft package.json Validität.
     */
    public function validate(): bool
    {
        if ($this->projectRoot === null) {
            return false;
        }

        $packageJson = $this->projectRoot->toString() . '/package.json';

        if (! file_exists($packageJson)) {
            return false;
        }

        $content = file_get_contents($packageJson);
        $data = json_decode($content, true);

        // Grundlegende Validierung
        return is_array($data)
            && isset($data['name'])
            && isset($data['version']);
    }

    /**
     * Initialisiert neues NPM-Projekt.
     */
    public function init(bool $yes = false): bool
    {
        $args = ['npm', 'init'];

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

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

        return $result->isSuccess();
    }

    /**
     * Installiert spezifisches Package.
     */
    public function installPackage(string $package, bool $dev = false, bool $exact = false): bool
    {
        $args = ['npm', 'install', $package];

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

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

        $result = $this->process->run(
            command: Command::fromArray($args),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromMinutes(3)
        );

        return $result->isSuccess();
    }

    /**
     * Deinstalliert spezifisches Package.
     */
    public function uninstallPackage(string $package): bool
    {
        $result = $this->process->run(
            command: Command::fromArray(['npm', 'uninstall', $package]),
            workingDirectory: $this->projectRoot,
            timeout: Duration::fromMinutes(2)
        );

        return $result->isSuccess();
    }

    /**
     * Zeigt Package-Informationen.
     */
    public function showPackageInfo(string $package): ?array
    {
        $result = $this->process->run(
            command: Command::fromArray(['npm', 'show', $package, '--json']),
            timeout: Duration::fromSeconds(30)
        );

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

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

        return is_array($data) ? $data : null;
    }
}
