<?php

declare(strict_types=1);

namespace App\Framework\Http\Session\ValueObjects;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Http\IpAddress;

/**
 * Value Object representing security-related session data
 */
final readonly class SecurityData
{
    /**
     * @param string $key Security data key (e.g., 'csrf_token', 'login_attempts')
     * @param mixed $value Security data value
     * @param Timestamp $timestamp When the data was created/updated
     * @param ?IpAddress $ipAddress IP address associated with this data
     * @param array<string, mixed> $metadata Additional security metadata
     */
    public function __construct(
        public string $key,
        public mixed $value,
        public Timestamp $timestamp,
        public ?IpAddress $ipAddress = null,
        public array $metadata = []
    ) {
    }

    /**
     * Create from array data
     * @param array<string, mixed> $data
     */
    public static function fromArray(array $data): self
    {
        $ipAddress = null;
        if (isset($data['ip_address']) && is_string($data['ip_address'])) {
            $ipAddress = new IpAddress($data['ip_address']);
        }

        return new self(
            key: $data['key'] ?? '',
            value: $data['value'] ?? null,
            timestamp: isset($data['timestamp'])
                ? Timestamp::fromFloat((float) $data['timestamp'])
                : Timestamp::fromFloat(microtime(true)),
            ipAddress: $ipAddress,
            metadata: $data['metadata'] ?? []
        );
    }

    /**
     * Create new security data
     */
    public static function create(string $key, mixed $value, ?IpAddress $ipAddress = null, array $metadata = []): self
    {
        return new self(
            key: $key,
            value: $value,
            timestamp: Timestamp::fromFloat(microtime(true)),
            ipAddress: $ipAddress,
            metadata: $metadata
        );
    }

    /**
     * Create CSRF token data
     */
    public static function csrfToken(string $token, ?IpAddress $ipAddress = null): self
    {
        return self::create('csrf_token', $token, $ipAddress, ['type' => 'csrf']);
    }

    /**
     * Create login attempt data
     */
    public static function loginAttempt(int $attempts, ?IpAddress $ipAddress = null): self
    {
        return self::create('login_attempts', $attempts, $ipAddress, [
            'type' => 'login_attempt',
            'max_attempts' => 5,
        ]);
    }

    /**
     * Create session fingerprint data
     */
    public static function sessionFingerprint(string $fingerprint, ?IpAddress $ipAddress = null): self
    {
        return self::create('session_fingerprint', $fingerprint, $ipAddress, ['type' => 'fingerprint']);
    }

    /**
     * Convert to array
     * @return array<string, mixed>
     */
    public function toArray(): array
    {
        return [
            'key' => $this->key,
            'value' => $this->value,
            'timestamp' => $this->timestamp->toFloat(),
            'ip_address' => $this->ipAddress?->toString(),
            'metadata' => $this->metadata,
        ];
    }

    /**
     * Get metadata value
     */
    public function getMetadata(string $key, mixed $default = null): mixed
    {
        return $this->metadata[$key] ?? $default;
    }

    /**
     * Check if data is older than given seconds
     */
    public function isOlderThan(int $seconds): bool
    {
        return $this->timestamp->toFloat() < (time() - $seconds);
    }

    /**
     * Get age in seconds
     */
    public function getAge(): int
    {
        return (int) (time() - $this->timestamp->toFloat());
    }

    /**
     * Check if data is expired (based on metadata TTL)
     */
    public function isExpired(): bool
    {
        $ttl = $this->getMetadata('ttl');
        if ($ttl === null) {
            return false;
        }

        return $this->isOlderThan((int) $ttl);
    }

    /**
     * Check if IP address matches
     */
    public function matchesIp(IpAddress $ipAddress): bool
    {
        return $this->ipAddress?->toString() === $ipAddress->toString();
    }

    /**
     * Create new instance with updated value
     */
    public function withValue(mixed $value): self
    {
        return new self(
            key: $this->key,
            value: $value,
            timestamp: Timestamp::fromFloat(microtime(true)), // Update timestamp when value changes
            ipAddress: $this->ipAddress,
            metadata: $this->metadata
        );
    }

    /**
     * Create new instance with additional metadata
     */
    public function withMetadata(string $key, mixed $value): self
    {
        $metadata = $this->metadata;
        $metadata[$key] = $value;

        return new self(
            key: $this->key,
            value: $this->value,
            timestamp: $this->timestamp,
            ipAddress: $this->ipAddress,
            metadata: $metadata
        );
    }

    /**
     * Create new instance with updated IP address
     */
    public function withIpAddress(?IpAddress $ipAddress): self
    {
        return new self(
            key: $this->key,
            value: $this->value,
            timestamp: $this->timestamp,
            ipAddress: $ipAddress,
            metadata: $this->metadata
        );
    }

    /**
     * Check if this is a CSRF token
     */
    public function isCsrfToken(): bool
    {
        return $this->key === 'csrf_token' || $this->getMetadata('type') === 'csrf';
    }

    /**
     * Check if this is login attempt data
     */
    public function isLoginAttempt(): bool
    {
        return $this->key === 'login_attempts' || $this->getMetadata('type') === 'login_attempt';
    }

    /**
     * Check if this is session fingerprint data
     */
    public function isSessionFingerprint(): bool
    {
        return $this->key === 'session_fingerprint' || $this->getMetadata('type') === 'fingerprint';
    }
}
