<?php

namespace App\Entity;

use App\Enum\DeviceStatus;
use App\Repository\DeviceRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

#[ORM\Entity(repositoryClass: DeviceRepository::class)]
#[ORM\Table(name: 'devices')]
#[ORM\Index(name: 'idx_device_family_status', columns: ['family_id', 'status'])]
#[ORM\UniqueConstraint(name: 'uniq_device_credential', columns: ['credential_digest'])]
class Device implements UserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne]
    #[ORM\JoinColumn(nullable: false, onDelete: 'RESTRICT')]
    private Family $family;

    #[ORM\Column(length: 100)]
    private string $label;

    #[ORM\Column(length: 16, enumType: DeviceStatus::class)]
    private DeviceStatus $status = DeviceStatus::Pending;

    #[ORM\Column(length: 64, nullable: true)]
    private ?string $pairingCodeDigest = null;

    #[ORM\Column(nullable: true)]
    private ?\DateTimeImmutable $pairingExpiresAt = null;

    #[ORM\Column(options: ['default' => 0])]
    private int $pairingAttempts = 0;

    #[ORM\Column(length: 64, nullable: true)]
    private ?string $credentialDigest = null;

    #[ORM\Column(nullable: true)]
    private ?\DateTimeImmutable $credentialExpiresAt = null;

    #[ORM\Column(nullable: true)]
    private ?\DateTimeImmutable $pairedAt = null;

    #[ORM\Column(nullable: true)]
    private ?\DateTimeImmutable $lastSeenAt = null;

    #[ORM\Column(nullable: true)]
    private ?\DateTimeImmutable $revokedAt = null;

    #[ORM\Column]
    private \DateTimeImmutable $createdAt;

    public function __construct(Family $family, string $label)
    {
        $this->family = $family;
        $this->label = trim($label);
        $this->createdAt = new \DateTimeImmutable();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getFamily(): Family
    {
        return $this->family;
    }

    public function getLabel(): string
    {
        return $this->label;
    }

    public function getStatus(): DeviceStatus
    {
        return $this->status;
    }

    public function getPairingCodeDigest(): ?string
    {
        return $this->pairingCodeDigest;
    }

    public function getPairingExpiresAt(): ?\DateTimeImmutable
    {
        return $this->pairingExpiresAt;
    }

    public function getPairingAttempts(): int
    {
        return $this->pairingAttempts;
    }

    public function getCredentialDigest(): ?string
    {
        return $this->credentialDigest;
    }

    public function getCredentialExpiresAt(): ?\DateTimeImmutable
    {
        return $this->credentialExpiresAt;
    }

    public function getLastSeenAt(): ?\DateTimeImmutable
    {
        return $this->lastSeenAt;
    }

    public function issuePairingCode(string $digest, \DateTimeImmutable $expiresAt): void
    {
        $this->status = DeviceStatus::Pending;
        $this->pairingCodeDigest = $digest;
        $this->pairingExpiresAt = $expiresAt;
        $this->pairingAttempts = 0;
        $this->credentialDigest = null;
        $this->credentialExpiresAt = null;
        $this->revokedAt = null;
    }

    public function recordPairingFailure(): void
    {
        ++$this->pairingAttempts;
    }

    public function activate(string $credentialDigest, \DateTimeImmutable $expiresAt): void
    {
        $now = new \DateTimeImmutable();
        $this->status = DeviceStatus::Active;
        $this->credentialDigest = $credentialDigest;
        $this->credentialExpiresAt = $expiresAt;
        $this->pairingCodeDigest = null;
        $this->pairingExpiresAt = null;
        $this->pairingAttempts = 0;
        $this->pairedAt = $now;
        $this->lastSeenAt = $now;
        $this->revokedAt = null;
    }

    public function refreshCredential(\DateTimeImmutable $expiresAt): void
    {
        $this->credentialExpiresAt = $expiresAt;
        $this->lastSeenAt = new \DateTimeImmutable();
    }

    public function markSeen(): void
    {
        $this->lastSeenAt = new \DateTimeImmutable();
    }

    public function revoke(): void
    {
        $this->status = DeviceStatus::Revoked;
        $this->credentialDigest = null;
        $this->credentialExpiresAt = null;
        $this->pairingCodeDigest = null;
        $this->pairingExpiresAt = null;
        $this->revokedAt = new \DateTimeImmutable();
    }

    public function isUsableAt(\DateTimeImmutable $now): bool
    {
        return DeviceStatus::Active === $this->status
            && null !== $this->credentialDigest
            && null !== $this->credentialExpiresAt
            && $this->credentialExpiresAt > $now;
    }

    public function getUserIdentifier(): string
    {
        return 'device:'.(string) $this->id;
    }

    public function getRoles(): array
    {
        return ['ROLE_DEVICE'];
    }

    public function eraseCredentials(): void
    {
    }
}
