<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Services;

use App\Framework\Core\ValueObjects\ClientIdentifier;
use App\Framework\Core\ValueObjects\Hash;
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\RateLimit\RateLimiter;
use App\Framework\RateLimit\RateLimitResult;

/**
 * LiveComponent Rate Limiter Service
 *
 * Integrates framework's RateLimit system with LiveComponents.
 * Provides component-level and action-level rate limiting.
 *
 * Rate Limit Key Format:
 * - Component-level: "livecomponent:{component}:{client_id}"
 * - Action-level: "livecomponent:{component}:{action}:{client_id}"
 *
 * Usage:
 * ```php
 * $result = $rateLimiter->checkActionLimit(
 *     component: $component,
 *     action: 'updateProfile',
 *     clientIdentifier: $request->clientIp
 * );
 *
 * if ($result->isExceeded()) {
 *     throw new RateLimitExceededException($result->getRetryAfter());
 * }
 * ```
 */
final readonly class LiveComponentRateLimiter
{
    public function __construct(
        private RateLimiter $rateLimiter
    ) {
    }

    /**
     * Check rate limit for component action
     *
     * Uses #[Action] attribute configuration or falls back to defaults.
     *
     * @param LiveComponentContract $component Component instance
     * @param string $action Action method name
     * @param ClientIdentifier $clientIdentifier Client identifier
     * @param Action|null $actionAttribute Action attribute for configuration
     * @return RateLimitResult Rate limit check result
     */
    public function checkActionLimit(
        LiveComponentContract $component,
        string $action,
        ClientIdentifier $clientIdentifier,
        ?Action $actionAttribute = null
    ): RateLimitResult {
        // Get rate limit from attribute or use default
        $limit = $actionAttribute?->rateLimit ?? $this->getDefaultActionLimit();

        if ($limit === null) {
            // No rate limiting configured
            return RateLimitResult::allowed(PHP_INT_MAX, 0);
        }

        // Build rate limit key
        $key = $this->buildActionKey($component, $action, $clientIdentifier);

        // Check limit (requests per minute)
        return $this->rateLimiter->checkLimit(
            key: $key,
            limit: $limit,
            window: 60.0 // 1 minute window
        );
    }

    /**
     * Check component-level rate limit (all actions combined)
     *
     * @param LiveComponentContract $component Component instance
     * @param ClientIdentifier $clientIdentifier Client identifier
     * @param int|null $limit Optional limit override (defaults to 60/min)
     * @return RateLimitResult Rate limit check result
     */
    public function checkComponentLimit(
        LiveComponentContract $component,
        ClientIdentifier $clientIdentifier,
        ?int $limit = null
    ): RateLimitResult {
        $limit ??= $this->getDefaultComponentLimit();

        $key = $this->buildComponentKey($component, $clientIdentifier);

        return $this->rateLimiter->checkLimit(
            key: $key,
            limit: $limit,
            window: 60.0
        );
    }

    /**
     * Reset rate limit for specific action
     *
     * Useful for admin operations or testing.
     */
    public function resetActionLimit(
        LiveComponentContract $component,
        string $action,
        ClientIdentifier $clientIdentifier
    ): void {
        $key = $this->buildActionKey($component, $action, $clientIdentifier);
        $this->rateLimiter->reset($key);
    }

    /**
     * Reset all rate limits for component
     */
    public function resetComponentLimit(
        LiveComponentContract $component,
        ClientIdentifier $clientIdentifier
    ): void {
        $key = $this->buildComponentKey($component, $clientIdentifier);
        $this->rateLimiter->reset($key);
    }

    /**
     * Get current usage for action
     *
     * @return int Number of requests in current window
     */
    public function getActionUsage(
        LiveComponentContract $component,
        string $action,
        ClientIdentifier $clientIdentifier
    ): int {
        $key = $this->buildActionKey($component, $action, $clientIdentifier);

        return $this->rateLimiter->getUsage($key, 60.0);
    }

    /**
     * Build rate limit key for action
     */
    private function buildActionKey(
        LiveComponentContract $component,
        string $action,
        ClientIdentifier $clientIdentifier
    ): string {
        $componentName = $this->getComponentName($component);
        $hashedClient = $this->hashClientIdentifier($clientIdentifier);

        return sprintf(
            'livecomponent:%s:%s:%s',
            $componentName,
            $action,
            $hashedClient
        );
    }

    /**
     * Build rate limit key for component
     */
    private function buildComponentKey(
        LiveComponentContract $component,
        ClientIdentifier $clientIdentifier
    ): string {
        $componentName = $this->getComponentName($component);
        $hashedClient = $this->hashClientIdentifier($clientIdentifier);

        return sprintf(
            'livecomponent:%s:%s',
            $componentName,
            $hashedClient
        );
    }

    /**
     * Get component name from class
     */
    private function getComponentName(LiveComponentContract $component): string
    {
        $className = get_class($component);
        $parts = explode('\\', $className);

        return strtolower(end($parts));
    }

    /**
     * Hash client identifier using framework Hash Value Object
     *
     * Uses SHA256 and returns first 16 characters for compact keys.
     */
    private function hashClientIdentifier(ClientIdentifier $identifier): string
    {
        return Hash::sha256($identifier->toString())->toShort(16);
    }

    /**
     * Get default action rate limit
     *
     * Default: 30 requests per minute per action
     */
    private function getDefaultActionLimit(): ?int
    {
        return 30;
    }

    /**
     * Get default component rate limit
     *
     * Default: 60 requests per minute for all actions combined
     */
    private function getDefaultComponentLimit(): int
    {
        return 60;
    }
}
