<?php

declare(strict_types=1);

namespace App\Framework\Exception\Authentication;

use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;

/**
 * Ausnahme für unzureichende Berechtigungen
 *
 * Verwendet OWASP-konforme Nachrichten für Authorization-Fehler
 */
final class InsufficientPrivilegesException extends FrameworkException
{
    /**
     * @param string $userId Benutzer-Identifikator
     * @param string $resource Geschützte Ressource
     * @param string $action Versuchte Aktion
     * @param array $requiredRoles Erforderliche Rollen/Berechtigungen
     * @param array $userRoles Aktuelle Benutzer-Rollen
     * @param \Throwable|null $previous Vorherige Ausnahme
     */
    public function __construct(
        public readonly string $userId,
        public readonly string $resource,
        public readonly string $action,
        public readonly array $requiredRoles = [],
        public readonly array $userRoles = [],
        ?\Throwable $previous = null
    ) {
        // OWASP-konforme Nachricht mit Platzhaltern
        $message = "User {$this->userId} access denied to {$this->resource}";

        $context = ExceptionContext::forOperation('authorization.access_check', 'Auth')
            ->withData([
                'user_identifier' => $this->userId,
                'resource' => $this->resource,
                'action' => $this->action,
                'required_roles' => $this->requiredRoles,
                'user_roles' => $this->userRoles,
                'missing_roles' => $this->getMissingRoles(),
                'event_identifier' => "authz_access_denied:{$this->userId},{$this->resource}",
                'category' => 'authorization',
                'requires_alert' => $this->requiresAlert(),
            ])
            ->withMetadata([
                'security_event' => true,
                'owasp_compliant' => true,
                'log_level' => 'WARN',
                'authorization_failure' => true,
            ]);

        parent::__construct(
            message: $message,
            context: $context,
            code: 403, // Forbidden
            previous: $previous,
            errorCode: AuthErrorCode::INSUFFICIENT_PRIVILEGES
        );
    }

    // === Factory Methods für verschiedene Authorization-Szenarien ===

    public static function resourceAccess(string $userId, string $resource, array $requiredRoles = []): self
    {
        return new self($userId, $resource, 'access', $requiredRoles);
    }

    public static function adminAction(string $userId, string $action): self
    {
        return new self($userId, 'admin_panel', $action, ['admin', 'super_admin']);
    }

    public static function apiEndpoint(string $userId, string $endpoint, array $requiredScopes = []): self
    {
        return new self($userId, $endpoint, 'api_call', $requiredScopes);
    }

    public static function fileAccess(string $userId, string $filePath, string $action = 'read'): self
    {
        return new self($userId, $filePath, $action, ['file_access']);
    }

    public static function dataAccess(string $userId, string $dataType, string $action = 'read'): self
    {
        return new self($userId, $dataType, $action, ['data_access']);
    }

    public static function privilegeEscalation(string $userId, string $targetRole): self
    {
        return new self($userId, $targetRole, 'privilege_escalation', ['admin']);
    }

    /**
     * Gibt OWASP-konforme Event-Daten zurück
     */
    public function getSecurityEventData(): array
    {
        return [
            'event_identifier' => "authz_access_denied:{$this->userId},{$this->resource}",
            'description' => "User {$this->userId} access denied to {$this->resource}",
            'category' => 'authorization',
            'log_level' => 'WARN',
            'requires_alert' => $this->requiresAlert(),
            'user_identifier' => $this->userId,
            'resource' => $this->resource,
            'action' => $this->action,
            'required_roles' => $this->requiredRoles,
            'user_roles' => $this->userRoles,
            'missing_roles' => $this->getMissingRoles(),
        ];
    }

    /**
     * Berechnet fehlende Rollen/Berechtigungen
     */
    public function getMissingRoles(): array
    {
        return array_diff($this->requiredRoles, $this->userRoles);
    }

    /**
     * Prüft ob Benutzer alle erforderlichen Rollen hat
     */
    public function hasRequiredRoles(): bool
    {
        return empty($this->getMissingRoles());
    }

    /**
     * Prüft ob der Access-Denial verdächtig ist und ein Alert erfordert
     */
    public function requiresAlert(): bool
    {
        // Alert bei Admin-Aktionen oder Privilege-Escalation-Versuchen
        if ($this->action === 'privilege_escalation') {
            return true;
        }

        if (str_contains($this->resource, 'admin') || str_contains($this->action, 'admin')) {
            return true;
        }

        // Alert bei sensiblen Ressourcen
        $sensitiveResources = ['user_data', 'financial_data', 'system_config', 'security_settings'];
        foreach ($sensitiveResources as $sensitive) {
            if (str_contains($this->resource, $sensitive)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Gibt benutzerfreundliche Fehlermeldung zurück
     */
    public function getUserMessage(): string
    {
        if (empty($this->requiredRoles)) {
            return "You don't have permission to access this resource.";
        }

        $missingRoles = $this->getMissingRoles();
        if (count($missingRoles) === 1) {
            return "You need the '{$missingRoles[0]}' role to access this resource.";
        } elseif (count($missingRoles) > 1) {
            $roleList = implode(', ', $missingRoles);

            return "You need one of these roles to access this resource: {$roleList}";
        }

        return "Access denied. Please contact your administrator for the required permissions.";
    }

    /**
     * Gibt Empfehlung für die nächsten Schritte zurück
     */
    public function getRecommendation(): string
    {
        if ($this->requiresAlert()) {
            return "Contact your system administrator for access to this resource.";
        }

        return "Please request the necessary permissions from your team lead or administrator.";
    }

    /**
     * Prüft ob es ein potentieller Privilege-Escalation-Versuch ist
     */
    public function isPrivilegeEscalationAttempt(): bool
    {
        return $this->action === 'privilege_escalation' ||
               str_contains($this->action, 'escalat') ||
               (in_array('admin', $this->requiredRoles) && ! in_array('admin', $this->userRoles));
    }
}
