<?php

declare(strict_types=1);

namespace App\Framework\Deployment\Pipeline\Stages;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Deployment\Pipeline\ValueObjects\DeploymentEnvironment;
use App\Framework\Deployment\Pipeline\ValueObjects\PipelineStage;
use App\Framework\Deployment\Pipeline\ValueObjects\StageResult;
use App\Framework\Logging\Logger;
use App\Framework\Process\Process;
use App\Framework\Process\ValueObjects\Command;

/**
 * Security Check Stage
 *
 * Performs security vulnerability scans before deployment.
 * Checks for:
 * - Composer dependency vulnerabilities
 * - NPM dependency vulnerabilities
 * - Code security issues
 */
final readonly class SecurityCheckStage implements PipelineStageInterface
{
    public function __construct(
        private Process $process,
        private Logger $logger
    ) {}

    public function getStage(): PipelineStage
    {
        return PipelineStage::SECURITY_CHECK;
    }

    public function execute(DeploymentEnvironment $environment): StageResult
    {
        if ($this->shouldSkip($environment)) {
            return StageResult::success(
                stage: $this->getStage(),
                duration: Duration::fromMilliseconds(0),
                output: 'Security checks skipped for development environment'
            );
        }

        $startTime = microtime(true);

        $this->logger->info('Running security checks', [
            'environment' => $environment->value
        ]);

        try {
            $vulnerabilities = [];

            // Check composer dependencies for known vulnerabilities
            $composerCheck = $this->checkComposerSecurity();
            if (!$composerCheck['passed']) {
                $vulnerabilities[] = $composerCheck['message'];
            }

            // Check npm dependencies for known vulnerabilities
            $npmCheck = $this->checkNpmSecurity();
            if (!$npmCheck['passed']) {
                $vulnerabilities[] = $npmCheck['message'];
            }

            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            if (!empty($vulnerabilities)) {
                return StageResult::failure(
                    stage: $this->getStage(),
                    duration: $duration,
                    error: 'Security vulnerabilities detected',
                    output: implode("\n", $vulnerabilities)
                );
            }

            return StageResult::success(
                stage: $this->getStage(),
                duration: $duration,
                output: 'No security vulnerabilities detected'
            );

        } catch (\Throwable $e) {
            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            $this->logger->error('Security check failed', [
                'error' => $e->getMessage()
            ]);

            return StageResult::failure(
                stage: $this->getStage(),
                duration: $duration,
                error: $e->getMessage()
            );
        }
    }

    public function canRollback(): bool
    {
        return false; // Security checks don't need rollback
    }

    public function rollback(DeploymentEnvironment $environment): StageResult
    {
        throw new \LogicException('Security check stage does not support rollback');
    }

    public function getDescription(): string
    {
        return $this->getStage()->getDescription();
    }

    public function shouldSkip(DeploymentEnvironment $environment): bool
    {
        // Skip security checks in development for faster iteration
        return $environment === DeploymentEnvironment::DEVELOPMENT;
    }

    /**
     * Check composer dependencies for security vulnerabilities
     */
    private function checkComposerSecurity(): array
    {
        try {
            // Use composer audit command (available in Composer 2.4+)
            $result = $this->process->run(
                Command::fromString('composer audit --format=json'),
                timeout: Duration::fromMinutes(2)
            );

            if (!$result->isSuccess()) {
                // If command not available, skip check
                if (str_contains($result->stderr, 'not found') || str_contains($result->stderr, 'Unknown command')) {
                    return [
                        'passed' => true,
                        'message' => 'Composer audit not available (requires Composer 2.4+)'
                    ];
                }

                return [
                    'passed' => false,
                    'message' => 'Composer security check failed: ' . $result->stderr
                ];
            }

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

            if (isset($auditData['advisories']) && count($auditData['advisories']) > 0) {
                return [
                    'passed' => false,
                    'message' => sprintf(
                        'Found %d composer security vulnerabilities',
                        count($auditData['advisories'])
                    )
                ];
            }

            return [
                'passed' => true,
                'message' => 'No composer vulnerabilities found'
            ];

        } catch (\Throwable $e) {
            // Don't fail deployment if security check fails
            $this->logger->warning('Composer security check error', [
                'error' => $e->getMessage()
            ]);

            return [
                'passed' => true,
                'message' => 'Composer security check skipped due to error'
            ];
        }
    }

    /**
     * Check npm dependencies for security vulnerabilities
     */
    private function checkNpmSecurity(): array
    {
        try {
            // Use npm audit
            $result = $this->process->run(
                Command::fromString('npm audit --json'),
                timeout: Duration::fromMinutes(2)
            );

            // npm audit exits with non-zero if vulnerabilities found
            // Parse JSON output to check severity
            $auditData = json_decode($result->stdout, true);

            if (isset($auditData['metadata'])) {
                $vulnerabilities = $auditData['metadata']['vulnerabilities'] ?? [];
                $critical = $vulnerabilities['critical'] ?? 0;
                $high = $vulnerabilities['high'] ?? 0;

                if ($critical > 0 || $high > 0) {
                    return [
                        'passed' => false,
                        'message' => sprintf(
                            'Found %d critical and %d high severity npm vulnerabilities',
                            $critical,
                            $high
                        )
                    ];
                }
            }

            return [
                'passed' => true,
                'message' => 'No critical npm vulnerabilities found'
            ];

        } catch (\Throwable $e) {
            // Don't fail deployment if security check fails
            $this->logger->warning('NPM security check error', [
                'error' => $e->getMessage()
            ]);

            return [
                'passed' => true,
                'message' => 'NPM security check skipped due to error'
            ];
        }
    }
}
