<?php

declare(strict_types=1);

namespace App\Framework\Console\Security\Middleware;

use App\Framework\Console\ExitCode;
use App\Framework\Console\Input\ConsoleInput;
use App\Framework\Console\Middleware\ConsoleMiddleware;
use App\Framework\Console\Output\ConsoleOutput;
use App\Framework\Console\Security\Attributes\RequiresPermission;
use App\Framework\Console\Security\SecurityException;
use App\Framework\Console\Security\SecurityService;
use App\Framework\Discovery\AttributeScanner;
use ReflectionMethod;

final readonly class SecurityMiddleware implements ConsoleMiddleware
{
    public function __construct(
        private SecurityService $securityService,
        private AttributeScanner $attributeScanner,
        private bool $enabled = true
    ) {
    }

    public function handle(ConsoleInput $input, ConsoleOutput $output, callable $next): ExitCode
    {
        if (! $this->enabled) {
            return $next($input, $output);
        }

        try {
            $this->checkCommandPermissions($input);

            return $next($input, $output);
        } catch (SecurityException $e) {
            $output->writeLine("<red>Access Denied: {$e->getMessage()}</red>");

            if ($output->isVerbose()) {
                $output->writeLine("<yellow>Current user:</yellow> {$e->user->name} ({$e->user->id})");
                $output->writeLine("<yellow>User permissions:</yellow> " .
                    implode(', ', array_map(fn ($p) => $p->value, $e->user->permissions)));

                $required = is_array($e->requiredPermissions)
                    ? array_map(fn ($p) => $p->value, $e->requiredPermissions)
                    : [$e->requiredPermissions->value];
                $output->writeLine("<yellow>Required permissions:</yellow> " . implode(', ', $required));
            }

            return ExitCode::PERMISSION_DENIED;
        }
    }

    private function checkCommandPermissions(ConsoleInput $input): void
    {
        $commandName = $this->extractCommandName($input);
        if (! $commandName) {
            return; // No command to check
        }

        // Find the command method and check for RequiresPermission attributes
        $commandClasses = $this->attributeScanner->findMethodsWithAttribute(
            \App\Framework\Console\Attributes\ConsoleCommand::class
        );

        foreach ($commandClasses as $class => $methods) {
            foreach ($methods as $method) {
                $commandAttribute = $this->attributeScanner->getMethodAttribute(
                    $class,
                    $method,
                    \App\Framework\Console\Attributes\ConsoleCommand::class
                );

                if ($commandAttribute && $commandAttribute->name === $commandName) {
                    $this->checkMethodPermissions($class, $method);

                    return;
                }
            }
        }
    }

    private function checkMethodPermissions(string $class, string $method): void
    {
        $reflectionMethod = new ReflectionMethod($class, $method);

        // Check method-level permissions
        $methodAttributes = $reflectionMethod->getAttributes(RequiresPermission::class);
        foreach ($methodAttributes as $attribute) {
            $requiresPermission = $attribute->newInstance();
            if (! $this->securityService->checkRequiresPermissionAttribute($requiresPermission)) {
                throw new SecurityException(
                    "Insufficient permissions for command method",
                    $requiresPermission->getPermissions(),
                    $this->securityService->getCurrentUser()
                );
            }
        }

        // Check class-level permissions
        $classAttributes = $reflectionMethod->getDeclaringClass()->getAttributes(RequiresPermission::class);
        foreach ($classAttributes as $attribute) {
            $requiresPermission = $attribute->newInstance();
            if (! $this->securityService->checkRequiresPermissionAttribute($requiresPermission)) {
                throw new SecurityException(
                    "Insufficient permissions for command class",
                    $requiresPermission->getPermissions(),
                    $this->securityService->getCurrentUser()
                );
            }
        }
    }

    private function extractCommandName(ConsoleInput $input): ?string
    {
        $arguments = $input->getArguments();

        return $arguments[1] ?? null; // First argument after script name
    }
}
