<?php

declare(strict_types=1);

namespace App\Administration\Domain\Model\Session;

use App\Administration\Domain\Model\RefreshToken\TokenFamilyId;
use App\Administration\Domain\Model\User\UserId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

/**
 * Représente une session utilisateur active.
 *
 * Une session est identifiée par son familyId (lié aux token families).
 * Elle stocke les métadonnées lisibles par l'utilisateur :
 * - Appareil/navigateur (DeviceInfo)
 * - Localisation approximative (Location)
 * - Dates de création et dernière activité
 *
 * Invariants :
 * - Une session appartient à un seul utilisateur et tenant
 * - Le familyId est immutable (identifie la session)
 * - lastActivityAt >= createdAt
 *
 * Note sur les méthodes statiques :
 * Cette classe utilise des factory methods (create(), reconstitute()) suivant les
 * patterns DDD standards pour la création d'Aggregates. Bien que le projet suive
 * les principes "No Static" d'Elegant Objects, les factory methods pour Aggregates
 * sont une exception documentée car elles encapsulent la logique d'instanciation
 * et gardent le constructeur privé, préservant ainsi les invariants du domaine.
 *
 * @see Story 1.6 - Gestion des sessions
 */
final readonly class Session
{
    private function __construct(
        public TokenFamilyId $familyId,
        public UserId $userId,
        public TenantId $tenantId,
        public DeviceInfo $deviceInfo,
        public Location $location,
        public DateTimeImmutable $createdAt,
        public DateTimeImmutable $lastActivityAt,
    ) {
    }

    /**
     * Crée une nouvelle session lors de la connexion.
     */
    public static function create(
        TokenFamilyId $familyId,
        UserId $userId,
        TenantId $tenantId,
        DeviceInfo $deviceInfo,
        Location $location,
        DateTimeImmutable $createdAt,
    ): self {
        return new self(
            familyId: $familyId,
            userId: $userId,
            tenantId: $tenantId,
            deviceInfo: $deviceInfo,
            location: $location,
            createdAt: $createdAt,
            lastActivityAt: $createdAt,
        );
    }

    /**
     * Met à jour le timestamp de dernière activité.
     *
     * Appelé lors du refresh de token pour maintenir la session active.
     */
    public function updateActivity(DateTimeImmutable $at): self
    {
        return new self(
            familyId: $this->familyId,
            userId: $this->userId,
            tenantId: $this->tenantId,
            deviceInfo: $this->deviceInfo,
            location: $this->location,
            createdAt: $this->createdAt,
            lastActivityAt: $at,
        );
    }

    /**
     * Vérifie si cette session correspond à la session courante.
     */
    public function isCurrent(TokenFamilyId $currentFamilyId): bool
    {
        return $this->familyId->equals($currentFamilyId);
    }

    /**
     * Vérifie si cette session appartient à l'utilisateur donné.
     */
    public function belongsToUser(UserId $userId): bool
    {
        return $this->userId->equals($userId);
    }

    /**
     * Reconstitue une session depuis le stockage.
     *
     * @internal Pour usage Infrastructure uniquement
     */
    public static function reconstitute(
        TokenFamilyId $familyId,
        UserId $userId,
        TenantId $tenantId,
        DeviceInfo $deviceInfo,
        Location $location,
        DateTimeImmutable $createdAt,
        DateTimeImmutable $lastActivityAt,
    ): self {
        return new self(
            familyId: $familyId,
            userId: $userId,
            tenantId: $tenantId,
            deviceInfo: $deviceInfo,
            location: $location,
            createdAt: $createdAt,
            lastActivityAt: $lastActivityAt,
        );
    }
}
