<?php
declare(strict_types=1);

namespace App\Application\Security\Guards;

use App\Framework\Database\Example\UserRepository;
use App\Application\Security\Events\Auth\{
    AuthenticationSuccessEvent,
    AuthenticationFailedEvent,
    AccountLockedEvent
};
use App\Framework\Core\Events\EventDispatcher;
use App\Domain\User\{User};

final class AuthenticationGuard
{
    private const MAX_FAILED_ATTEMPTS = 5;
    private const LOCKOUT_DURATION = 900; // 15 Minuten

    public function __construct(
        private EventDispatcher $eventDispatcher,
        private UserRepository $userRepository
    ) {}

    public function authenticate(string $email, string $password): ?User
    {
        try {
            $user = $this->userRepository->findByEmail($email);

            if (!$user) {
                $this->dispatchFailedAttempt($email, 'user_not_found');
                return null;
            }

            if ($this->isAccountLocked($user)) {
                $this->dispatchAccountLocked($email, 'too_many_failed_attempts', $user->failed_attempts);
                return null;
            }

            if (!$this->verifyPassword($password, $user->password_hash)) {
                $this->handleFailedAttempt($user);
                return null;
            }

            $this->handleSuccessfulLogin($user);
            return $user;

        } catch (\Exception $e) {
            $this->dispatchFailedAttempt($email, 'authentication_error');
            throw $e;
        }
    }

    public function logout(User $user): void
    {
        $sessionId = session_id();

        // Session invalidieren
        session_destroy();

        $this->eventDispatcher->dispatch(new \App\Application\Security\Events\Auth\SessionTerminatedEvent(
            email: $user->email,
            sessionId: $sessionId,
            reason: 'manual_logout'
        ));
    }

    private function handleSuccessfulLogin(User $user): void
    {
        // Failed attempts zurücksetzen
        $user->failed_attempts = 0;
        $user->last_login = new \DateTimeImmutable();
        $this->userRepository->save($user);

        // Session regenerieren
        session_regenerate_id(true);
        $_SESSION['user_id'] = $user->id;

        $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent(
            email: $user->email,
            sessionId: session_id(),
            method: 'password'
        ));
    }

    private function handleFailedAttempt(User $user): void
    {
        $user->failed_attempts++;
        $user->last_failed_attempt = new \DateTimeImmutable();

        if ($user->failed_attempts >= self::MAX_FAILED_ATTEMPTS) {
            $user->locked_until = new \DateTimeImmutable('+' . self::LOCKOUT_DURATION . ' seconds');
            $this->userRepository->save($user);

            $this->dispatchAccountLocked($user->email, 'max_attempts_exceeded', $user->failed_attempts);
        } else {
            $this->userRepository->save($user);
            $this->dispatchFailedAttempt($user->email, 'invalid_password', $user->failed_attempts);
        }
    }

    private function dispatchFailedAttempt(string $email, string $reason, int $attempts = 1): void
    {
        $this->eventDispatcher->dispatch(new AuthenticationFailedEvent(
            email: $email,
            reason: $reason,
            failedAttempts: $attempts
        ));
    }

    private function dispatchAccountLocked(string $email, string $reason, int $attempts): void
    {
        $this->eventDispatcher->dispatch(new AccountLockedEvent(
            email: $email,
            reason: $reason,
            failedAttempts: $attempts
        ));
    }

    private function isAccountLocked(User $user): bool
    {
        return $user->locked_until && $user->locked_until > new \DateTimeImmutable();
    }

    private function verifyPassword(string $password, string $hash): bool
    {
        return password_verify($password, $hash);
    }
}
