<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\AgeVerification\Model;

use CpCreation\VitiCore\AgeVerification\Repository\AgeVerificationConfigRepository;
use CpCreation\VitiCore\Behavior\HasTimestamp;
use CpCreation\VitiCore\Behavior\Impl\Timestamp;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

#[ORM\Entity(repositoryClass: AgeVerificationConfigRepository::class)]
#[ORM\Table(name: 'site_age_verification_config')]
#[ORM\HasLifecycleCallbacks]
class AgeVerificationConfig implements HasTimestamp
{
    use Timestamp;

    #[ORM\Id]
    #[ORM\Column(type: 'uuid')]
    #[ORM\GeneratedValue(strategy: 'NONE')]
    private UuidInterface $id;

    #[ORM\Column(type: 'boolean', options: ['default' => false])]
    private bool $enabled = false;

    #[Assert\Country]
    #[ORM\Column(type: 'string', length: 2, options: ['default' => 'FR'])]
    private string $defaultCountryCode = 'FR';

    #[Assert\Range(min: 1, max: 99)]
    #[ORM\Column(type: 'smallint', options: ['default' => 21])]
    private int $defaultMinimumAge = 21;

    #[Assert\Range(min: 1, max: 365)]
    #[ORM\Column(type: 'smallint', options: ['default' => 30])]
    private int $rememberForDays = 30;

    /** @var Collection<int, AgeVerificationCountryRule> */
    #[Assert\Valid]
    #[ORM\OneToMany(
        targetEntity: AgeVerificationCountryRule::class,
        mappedBy: 'config',
        cascade: ['all'],
        orphanRemoval: true,
    )]
    #[ORM\OrderBy(['countryCode' => 'ASC'])]
    private Collection $countryRules;

    public function __construct()
    {
        $this->id = Uuid::uuid4();
        $this->countryRules = new ArrayCollection();
    }

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

    public function isEnabled(): bool
    {
        return $this->enabled;
    }

    public function setEnabled(bool $enabled): void
    {
        $this->enabled = $enabled;
    }

    public function getDefaultCountryCode(): string
    {
        return $this->defaultCountryCode;
    }

    public function setDefaultCountryCode(string $defaultCountryCode): void
    {
        $this->defaultCountryCode = mb_strtoupper(mb_trim($defaultCountryCode));
    }

    public function getDefaultMinimumAge(): int
    {
        return $this->defaultMinimumAge;
    }

    public function setDefaultMinimumAge(int $defaultMinimumAge): void
    {
        $this->defaultMinimumAge = $defaultMinimumAge;
    }

    public function getRememberForDays(): int
    {
        return $this->rememberForDays;
    }

    public function setRememberForDays(int $rememberForDays): void
    {
        $this->rememberForDays = $rememberForDays;
    }

    /** @return Collection<int, AgeVerificationCountryRule> */
    public function getCountryRules(): Collection
    {
        return $this->countryRules;
    }

    public function addCountryRule(AgeVerificationCountryRule $countryRule): void
    {
        if ($this->countryRules->contains($countryRule)) {
            return;
        }

        $countryRule->setConfig($this);
        $this->countryRules->add($countryRule);
    }

    public function removeCountryRule(AgeVerificationCountryRule $countryRule): void
    {
        if (!$this->countryRules->removeElement($countryRule)) {
            return;
        }

        $countryRule->setConfig(null);
    }

    public function resolveMinimumAge(string $countryCode): int
    {
        $normalizedCountryCode = mb_strtoupper(mb_trim($countryCode));

        foreach ($this->countryRules as $countryRule) {
            if ($normalizedCountryCode === $countryRule->getCountryCode()) {
                return $countryRule->getMinimumAge();
            }
        }

        return CountryLegalAge::forCountry($normalizedCountryCode)?->value ?? $this->defaultMinimumAge;
    }

    public function getProofFingerprint(): string
    {
        $countryRules = [];
        foreach ($this->countryRules as $countryRule) {
            $countryRules[$countryRule->getCountryCode()] = $countryRule->getMinimumAge();
        }
        ksort($countryRules);

        return hash('sha256', json_encode([
            'id' => $this->id->toString(),
            'defaultCountryCode' => $this->defaultCountryCode,
            'defaultMinimumAge' => $this->defaultMinimumAge,
            'rememberForDays' => $this->rememberForDays,
            'countryLegalAgeDataVersion' => CountryLegalAge::DATA_VERSION,
            'countryRules' => $countryRules,
        ], JSON_THROW_ON_ERROR));
    }

    #[Assert\Callback]
    public function validateCountryRules(ExecutionContextInterface $context): void
    {
        $seenCountries = [];

        foreach ($this->countryRules as $index => $countryRule) {
            $countryCode = $countryRule->getCountryCode();
            if (isset($seenCountries[$countryCode])) {
                $context->buildViolation('age_verification.admin.country_duplicate')
                    ->atPath(sprintf('countryRules[%d].countryCode', $index))
                    ->addViolation();
            }

            $seenCountries[$countryCode] = true;
        }
    }
}
