<?php

declare(strict_types=1);

namespace App\Service\Shop;

use App\ApiResources\Organization\OrganizationCreationRequest;
use App\ApiResources\Shop\NewStructureArtistPremiumTrialRequest;
use App\Entity\Organization\Organization;
use App\Entity\Organization\Subdomain;
use App\Entity\Shop\ShopRequest;
use App\Enum\Access\AccessIdsEnum;
use App\Enum\Organization\SettingsProductEnum;
use App\Enum\Shop\ShopRequestStatus;
use App\Enum\Shop\ShopRequestType;
use App\Message\Message\Shop\NewStructureArtistPremiumTrial;
use App\Repository\Organization\OrganizationRepository;
use App\Service\ApiLegacy\ApiLegacyRequestService;
use App\Service\Dolibarr\DolibarrApiService;
use App\Service\Dolibarr\DolibarrUtils;
use App\Service\Mailer\Mailer;
use App\Service\Mailer\Model\NewStructureArtistPremiumTrialRequestValidationModel;
use App\Service\Mailer\Model\SubdomainChangeModel;
use App\Service\Organization\OrganizationFactory;
use App\Service\Utils\UrlBuilder;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\EntityManagerInterface;
use JsonException;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Messenger\Exception\ExceptionInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Uid\Uuid;

/**
 * Service for managing shop requests.
 */
readonly class ShopService
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private Mailer                 $mailer,
        private string                 $publicBaseUrl,
        private OrganizationFactory    $organizationFactory,
        private SerializerInterface    $serializer,
        private LoggerInterface        $logger,
        private DolibarrApiService     $dolibarrApiService,
        private DolibarrUtils          $dolibarrUtils,
        private MessageBusInterface    $messageBus,
    ) {
    }

    /**
     * A new shop request has been submitted.
     * Register the request, and send the validation link by email.
     *
     * @param ShopRequestType $type
     * @param array<string, mixed> $data
     * @return ShopRequest
     * @throws TransportExceptionInterface
     */
    public function registerNewShopRequest(ShopRequestType $type, array $data): ShopRequest
    {
        $this->controlShopRequestData($type, $data);
        $request = $this->createRequest($type, $data);
        $this->sendRequestValidationLink($request);

        return $request;
    }

    /**
     * Validate the shop request based on its type.
     * For NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL, check if the organization already exists.
     * For other types, throw an error.
     *
     * @param ShopRequestType $type
     * @param array<string, mixed> $data
     */
    protected function controlShopRequestData(ShopRequestType $type, array $data): void
    {
        if ($type === ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL) {
            // Create a minimal OrganizationCreationRequest with required info
            $organizationCreationRequest = new OrganizationCreationRequest();
            $organizationCreationRequest->setName($data['structureName'] ?? '');
            $organizationCreationRequest->setCity($data['city'] ?? '');
            $organizationCreationRequest->setPostalCode($data['postalCode'] ?? '');
            $organizationCreationRequest->setStreetAddress1($data['address'] ?? '');
            $organizationCreationRequest->setStreetAddress2($data['addressComplement'] ?? '');
            $organizationCreationRequest->setStreetAddress3('');

            // Check if organization already exists
            $this->organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
        } else {
            throw new RuntimeException('request type not supported');
        }
    }

    /**
     * Validate the request and dispatch the appropriate job based on the request type.
     *
     * @throws RuntimeException|ExceptionInterface
     */
    public function processShopRequest(ShopRequest $shopRequest): void
    {
        // Dispatch appropriate job based on request type
        switch ($shopRequest->getType()->value) {
            case ShopRequestType::NEW_STRUCTURE_ARTIST_PREMIUM_TRIAL->value:
                $this->messageBus->dispatch(
                    new NewStructureArtistPremiumTrial($shopRequest->getToken())
                );
                break;
            default:
                throw new RuntimeException('request type not supported');
        }

        $shopRequest->setStatus(ShopRequestStatus::VALIDATED);
        $this->entityManager->persist($shopRequest);
        $this->entityManager->flush();
    }

    /**
     * Create and persist a new ShopRequest entity.
     * @param ShopRequestType $type
     * @param array<string, mixed> $data
     * @return ShopRequest
     */
    protected function createRequest(ShopRequestType $type, array $data): ShopRequest
    {
        $shopRequest = new ShopRequest();
        $shopRequest->setToken(Uuid::v4()->toRfc4122());
        $shopRequest->setType($type);
        $shopRequest->setData($data);

        $this->entityManager->persist($shopRequest);
        $this->entityManager->flush();

        return $shopRequest;
    }

    /**
     * Send validation email with link.
     *
     * @throws TransportExceptionInterface
     */
    protected function sendRequestValidationLink(ShopRequest $shopRequest): void
    {
        $validationUrl = UrlBuilder::concat(
            $this->publicBaseUrl,
            ['/api/public/shop/validate', $shopRequest->getToken()]
        );

        $data = $shopRequest->getData();

        $model = new NewStructureArtistPremiumTrialRequestValidationModel();
        $model
            ->setSenderId(AccessIdsEnum::ADMIN_2IOPENSERVICE->value)
            ->setToken($shopRequest->getToken())
            ->setRepresentativeEmail($data['representativeEmail'] ?? '')
            ->setRepresentativeFirstName($data['representativeFirstName'] ?? '')
            ->setRepresentativeLastName($data['representativeLastName'] ?? '')
            ->setStructureName($data['structureName'] ?? '')
            ->setValidationUrl($validationUrl);

        $this->mailer->main($model);

        $shopRequest->setStatus(ShopRequestStatus::ACTIVATION_LINK_SENT);
        $this->entityManager->persist($shopRequest);
        $this->entityManager->flush();
    }

    /**
     * Start an artist premium trial for an organization.
     *
     * @param Organization $organization The organization to start the trial for
     * @param NewStructureArtistPremiumTrialRequest $request The trial request data
     *
     * @throws Exception
     * @throws JsonException
     */
    public function startArtistPremiumTrial(Organization $organization, NewStructureArtistPremiumTrialRequest $request): void
    {
        // Update settings
        $settings = $organization->getSettings();
        $settings->setProductBeforeTrial( $organization->getSettings()->getProduct());
        $settings->setTrialActive(true);
        $settings->setLastTrialStartDate(new \DateTime('now'));
        $settings->setProduct(SettingsProductEnum::ARTIST_PREMIUM);
        $this->entityManager->persist($settings);
        $this->entityManager->flush();

        $dolibarrSocietyId = $this->dolibarrApiService->getSocietyId($organization->getId());

        // Create contract in dolibarr
        $dolibarrProductId = $this->dolibarrUtils->getProductId(
            SettingsProductEnum::ARTIST_PREMIUM, true
        );

        $contractId = $this->dolibarrApiService->createContract(
            $dolibarrSocietyId, $dolibarrProductId, true, 1
        );

        $this->dolibarrApiService->createContractLine($contractId, $dolibarrProductId, 1);

        // Maj le représentant commercial dans dolibarr
        $this->dolibarrUtils->updateSocietyCommercialsWithApi($dolibarrSocietyId);

        // Met à jour le produit dans Dolibarr
        $productName = $this->dolibarrUtils->getDolibarrProductName(SettingsProductEnum::ARTIST_PREMIUM, true);
        $this->dolibarrApiService->updateSocietyProduct($dolibarrSocietyId, $productName);

        // Ajoute une entrée aux actions commerciales dolibarr
        $message = sprintf(
            "Action réalisé par : %s %s.<br>Fonction : %s<br>Mail:%s<br>Tel:%s",
            $request->getRepresentativeFirstName(),
            $request->getRepresentativeLastName(),
            $request->getRepresentativeFunction(),
            $request->getRepresentativeEmail(),
            $request->getRepresentativePhone()
        );

        $this->dolibarrUtils->addActionComm(
            $dolibarrSocietyId, "Ouverture de la période d’essai", $message
        );
    }

    protected function handleNewStructureArtistPremiumTrialRequest(string $token): void
    {
        // Retrieve the ShopRequest entity using its token
        $shopRequest = $this->entityManager->find(ShopRequest::class, $token);

        if (!$shopRequest) {
            $this->logger->error('Cannot find ShopRequest with token: ' . $token);
            return;
        }

        // Convert the stored JSON data to a NewStructureArtistPremiumTrialRequest object
        $data = $shopRequest->getData();
        $trialRequest = $this->serializer->deserialize(
            json_encode($data),
            NewStructureArtistPremiumTrialRequest::class,
            'json'
        );

        $organization = $this->createOrganization($trialRequest);

        // Start the artist premium trial
        $this->startArtistPremiumTrial($organization, $trialRequest);

        $this->logger->info('Successfully processed NewStructureArtistPremiumTrial for token: ' . $token);
    }

    protected function createOrganization(NewStructureArtistPremiumTrialRequest $trialRequest): Organization
    {
        // Generate an OrganizationCreationRequest object
        $organizationCreationRequest = new OrganizationCreationRequest();
        $organizationCreationRequest->setName($trialRequest->getStructureName());
        $organizationCreationRequest->setStreetAddress1($trialRequest->getAddress());
        $organizationCreationRequest->setStreetAddress2($trialRequest->getAddressComplement());
        $organizationCreationRequest->setPostalCode($trialRequest->getPostalCode());
        $organizationCreationRequest->setCity($trialRequest->getCity());
        $organizationCreationRequest->setEmail($trialRequest->getStructureEmail());
        $organizationCreationRequest->setPrincipalType($trialRequest->getStructureType());
        $organizationCreationRequest->setLegalStatus($trialRequest->getLegalStatus());
        $organizationCreationRequest->setSiretNumber($trialRequest->getSiren());

        // TODO: à améliorer
        $organizationCreationRequest->setPhoneNumber(
            '+33' . substr($trialRequest->getRepresentativePhone(), 1)
        );

        // Generate a subdomain from the structure name
        // TODO: à améliorer
        $subdomain = $this->generateSubdomain($trialRequest->getStructureName());
        $organizationCreationRequest->setSubdomain($subdomain);

        // Set default values
        $organizationCreationRequest->setProduct(SettingsProductEnum::FREEMIUM);
        $organizationCreationRequest->setCreateWebsite(false);
        $organizationCreationRequest->setClient(false);
        $organizationCreationRequest->setCreationDate(new \DateTime());

        // Create the organization
        return $this->organizationFactory->create($organizationCreationRequest);
    }

    /**
     * Generate a subdomain from a structure name.
     */
    private function generateSubdomain(string $name): string
    {
        // Remove accents and special characters
        $subdomain = iconv('UTF-8', 'ASCII//TRANSLIT', $name);
        // Replace spaces and special characters with hyphens
        $subdomain = preg_replace('/[^a-zA-Z0-9]/', '-', $subdomain);
        // Convert to lowercase
        $subdomain = strtolower($subdomain);
        // Remove consecutive hyphens
        $subdomain = preg_replace('/-+/', '-', $subdomain);
        // Trim hyphens from beginning and end
        $subdomain = trim($subdomain, '-');
        // Limit length
        $subdomain = substr($subdomain, 0, 30);

        return $subdomain;
    }
}
