<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Settings;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\IntegrationConfig;
use Ferienpass\CoreBundle\Entity\IntegrationCredential;
use Ferienpass\CoreBundle\Tenant\TenantContext;
use ParagonIE\Halite\HiddenString;
use ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
use ParagonIE\Halite\Symmetric\EncryptionKey;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class IntegrationsManager
{
    private const CACHE_KEY_CONFIG_PREFIX = 'ferienpass.integration_config.';
    private const CACHE_KEY_CONFIGURED_PREFIX = 'ferienpass.integration_configured.';

    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly CacheItemPoolInterface $appConfigCache,
        private readonly TenantContext $tenantContext,
        #[Autowire(param: 'kernel.environment')]
        private readonly string $environment,
        #[Autowire(param: 'env(INTEGRATIONS_ENCRYPTION_KEY)')]
        private readonly string $encryptionKey,
        #[Autowire(param: 'env(default::OPENAI_API_KEY)')]
        private readonly ?string $openAiApiKeyEnv = null,
        #[Autowire(param: 'env(default::BREVO_API_KEY)')]
        private readonly ?string $brevoSmsApiKeyEnv = null,
        #[Autowire(param: 'env(default::PMPAYMENT_AGS)')]
        private readonly ?string $pmPaymentAgsEnv = null,
        #[Autowire(param: 'env(default::PMPAYMENT_PROCEDURE)')]
        private readonly ?string $pmPaymentProcedureEnv = null,
        #[Autowire(param: 'env(default::PMPAYMENT_SALT)')]
        private readonly ?string $pmPaymentSaltEnv = null,
    ) {
    }

    public function isOpenAiApiKeyFromEnv(): bool
    {
        return null !== $this->openAiApiKeyEnv && '' !== $this->openAiApiKeyEnv;
    }

    public function isBrevoSmsApiKeyFromEnv(): bool
    {
        return null !== $this->brevoSmsApiKeyEnv && '' !== $this->brevoSmsApiKeyEnv;
    }

    public function isPmPaymentFromEnv(): bool
    {
        return null !== $this->pmPaymentAgsEnv && '' !== $this->pmPaymentAgsEnv;
    }

    public function isIntegrationConfigured(string $integration): bool
    {
        return null !== $this->getIntegrationConfig($integration);
    }

    public function getIntegrationConfig(string $integration): ?array
    {
        $cacheKey = $this->tenantContext->cacheKey(self::CACHE_KEY_CONFIG_PREFIX.$integration);
        $item = $this->appConfigCache->getItem($cacheKey);
        if ($item->isHit()) {
            return $item->get();
        }

        /** @var IntegrationConfig|null $entity */
        $entity = $this->entityManager->getRepository(IntegrationConfig::class)->findOneBy(['provider' => $integration]);

        $config = $entity?->getConfig() ?? [];

        if ('gpt_image_generation' === $integration && $this->isOpenAiApiKeyFromEnv()) {
            $config['OPENAI_API_KEY'] = $this->openAiApiKeyEnv;
        }

        if ('brevo_sms' === $integration && $this->isBrevoSmsApiKeyFromEnv()) {
            $config['BREVO_API_KEY'] = $this->brevoSmsApiKeyEnv;
        }

        if ('PmPaymentProvider' === $integration && $this->isPmPaymentFromEnv()) {
            $config['pmPaymentAgs'] = $this->pmPaymentAgsEnv;
            $config['pmPaymentProcedure'] = $this->pmPaymentProcedureEnv;
            $config['pmPaymentSalt'] = $this->pmPaymentSaltEnv;
        }

        /** @var IntegrationCredential|null $credential */
        $credential = $entity?->getCredentials()->filter(fn (IntegrationCredential $c) => $c->getEnv() === $this->environment)->first() ?: null;

        if (null !== $credential) {
            $credentialConfig = $credential->getConfig();
            foreach ($credentialConfig as $key => $value) {
                if (\is_string($value)) {
                    try {
                        $credentialConfig[$key] = $this->decrypt($value);
                    } catch (\Throwable) {
                        // Skip if not decryptable (e.g. migration or wrongly saved)
                    }
                }
            }
            $config = array_merge($config, $credentialConfig);
        }

        if (empty($config)) {
            return null;
        }

        $item->set($config);
        $this->appConfigCache->save($item);

        return $config;
    }

    public function saveIntegrationConfig(string $integration, array $config): void
    {
        /** @var IntegrationConfig|null $entity */
        $entity = $this->entityManager->getRepository(IntegrationConfig::class)->findOneBy(['provider' => $integration]);

        if (null === $entity) {
            $entity = new IntegrationConfig($integration);
            $this->entityManager->persist($entity);
        }

        /** @var IntegrationCredential|null $credential */
        $credential = $entity->getCredentials()->filter(fn (IntegrationCredential $c) => $c->getEnv() === $this->environment)->first() ?: null;

        if (null === $credential) {
            $credential = new IntegrationCredential($this->environment);
            $credential->setIntegrationConfig($entity);
            $entity->addCredential($credential);
            $this->entityManager->persist($credential);
        }

        $credentialConfig = [];
        $baseConfig = [];

        // Distinguish between sensitive (to be encrypted) and base config
        // For now, we encrypt everything in credentials as before
        foreach ($config as $key => $value) {
            if (\is_string($value)) {
                $credentialConfig[$key] = $this->encrypt($value);
            } else {
                $baseConfig[$key] = $value;
            }
        }

        $credential->setConfig($credentialConfig);
        $entity->setConfig(array_merge($entity->getConfig(), $baseConfig));

        $this->entityManager->flush();

        $this->appConfigCache->deleteItem($this->tenantContext->cacheKey(self::CACHE_KEY_CONFIG_PREFIX.$integration));
        $this->appConfigCache->deleteItem($this->tenantContext->cacheKey(self::CACHE_KEY_CONFIGURED_PREFIX.$integration));
    }

    public function encrypt(string $plaintext): string
    {
        return SymmetricCrypto::encrypt(new HiddenString($plaintext), $this->getEncryptionKey());
    }

    public function decrypt(string $ciphertext): string
    {
        return SymmetricCrypto::decrypt($ciphertext, $this->getEncryptionKey())->getString();
    }

    private function getEncryptionKey(): EncryptionKey
    {
        return new EncryptionKey(new HiddenString(hash('sha256', $this->encryptionKey, true)));
    }
}
