<?php

declare(strict_types=1);

namespace App\Service\Dolibarr;

use App\Entity\Organization\Organization;
use App\Enum\Dolibarr\DolibarrDocTypeEnum;
use App\Enum\Organization\SettingsProductEnum;
use App\Service\Rest\ApiRequestService;
use App\Service\Utils\DatesUtils;
use Exception;
use JetBrains\PhpStorm\Pure;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
 * Service d'appel à l'API dolibarr.
 *
 * @see https://prod-erp.2iopenservice.com/api/index.php/explorer/
 */
class DolibarrApiService extends ApiRequestService
{
    /** @noinspection SenselessProxyMethodInspection Method shall be kept to allow dependency injections, even if empty */
    #[Pure]
    public function __construct(HttpClientInterface $dolibarr_client)
    {
        parent::__construct($dolibarr_client);
    }

    /**
     * Get a dolibarr society by its opentalent organization id.
     *
     * @return array<mixed>|null
     *
     * @throws \JsonException
     */
    public function getSociety(int $organizationId): ?array
    {
        // impossible to retrieve a society by its extrafield 2iopen_organization_id (thanks dolibarr), so
        // we need to store the organization id in two fields: 2iopen_organization_id and ref_int :(
        try {
            return $this->getJsonContent(
                'thirdparties',
                [
                    'limit' => '1',
                    'sqlfilters' => '(ef.2iopen_organization_id:=:'.$organizationId.')',
                ]
            )[0];
        } catch (HttpException $e) {
            if ($e->getStatusCode() === 404) {
                // /!\ The dolibarr API will return a 404 error if no results are found...
                return null;
            }
            throw $e;
        }
    }

    /**
     * Get the dolibarr id of an organization
     *
     * @param int $organizationId
     * @return int|null
     * @throws \JsonException
     */
    public function getSocietyId(int $organizationId): ?int
    {
        $society = $this->getSociety($organizationId);
        return $society ? (int)$society['id'] : null;
    }

    /**
     * Get the first active contract for the given dolibarr society.
     *
     * @return array<mixed>|null
     */
    public function getActiveContract(int $socId): ?array
    {
        try {
            return $this->getJsonContent(
                'contracts',
                ['limit' => '1', 'sqlfilters' => 'statut:=:1', 'thirdparty_ids' => $socId]
            )[0];
        } catch (HttpException $e) {
            if ($e->getStatusCode() === 404) {
                // /!\ The dolibarr API will return a 404 error if no results are found...
                return null;
            }
            throw $e;
        }
    }

    /**
     * Get a society bills by their society id.
     *
     * @return array<mixed>
     */
    public function getBills(int $socId): array
    {
        try {
            return $this->getJsonContent(
                'invoices',
                [
                    'sortfield' => 'datef',
                    'sortorder' => 'DESC',
                    'limit' => 5,
                    'sqlfilters' => "(fk_soc:=:$socId) and (fk_statut:!=:0)",
                ]);
        } catch (HttpException $e) {
            if ($e->getStatusCode() === 404) {
                // /!\ The dolibarr API will return a 404 error if no results are found...
                return [];
            }
            throw $e;
        }
    }

    /**
     * Get the last order of the given society.
     *
     * @return array<string, mixed>|null
     *
     * @throws \JsonException
     */
    public function getLastOrder(int $socId): ?array
    {
        try {
            $results = $this->getJsonContent(
                'orders',
                [
                    'sortfield' => 't.date_valid',
                    'sortorder' => 'DESC',
                    'limit' => 1,
                    'sqlfilters' => 'fk_soc:=:'.$socId,
                ]
            );
        } catch (HttpException $e) {
            if ($e->getStatusCode() === 404) {
                // /!\ The dolibarr API will return a 404 error if no results are found...
                return null;
            }
            throw $e;
        }

        return empty($results) ? null : $results[0];
    }

    /**
     * Get all the societies which are Opentalent client.
     *
     * @return array<mixed>
     *
     * @throws HttpException
     */
    public function getAllClients(): array
    {
        return $this->getJsonContent(
            'thirdparties',
            ['limit' => '1000000', 'sqlfilters' => 'client:=:1']
        );
    }

    /**
     * Get the society contacts.
     *
     * @return array<mixed>
     *
     * @throws HttpException
     */
    public function getContacts(int $socId): array
    {
        try {
            return $this->getJsonContent(
                'contacts',
                ['limit' => 1000, 'thirdparty_ids' => $socId],
            );
        } catch (HttpException $e) {
            if ($e->getStatusCode() === 404) {
                // /!\ The dolibarr API will return a 404 error if no results are found...
                return [];
            }
            throw $e;
        }
    }

    /**
     * Get the society contacts that have a non-null personId.
     *
     * @return array<mixed>
     *
     * @throws HttpException
     */
    public function getActiveOpentalentContacts(int $socId): array
    {
        // On est obligé ici de passer la query en dur, sinon les parenthèses sont encodées,
        // et dolibarr est pas content :(
        try {
            return $this->getJsonContent(
                'contacts?limit=1000&t.statut=1&thirdparty_ids='.$socId.'&sqlfilters:=:(te.2iopen_person_id%3A%3E%3A0)'
            );
        } catch (HttpException $e) {
            if ($e->getStatusCode() === 404) {
                // /!\ The dolibarr API will return a 404 error if no results are found...
                return [];
            }
            throw $e;
        }
    }

    /**
     * Get the society tags.
     *
     * @param int $socId The society ID
     *
     * @return array<int> The array of tags associated with the society
     *
     * @throws HttpException|\JsonException if an HTTP error occurs
     */
    public function getSocietyTagsIds(int $socId): array
    {
        try {
            return array_map(
                function ($x) { return (int) $x['id']; },
                $this->getJsonContent("/thirdparties/$socId/categories")
            );
        } catch (HttpException $e) {
            if ($e->getStatusCode() === 404) {
                // /!\ The dolibarr API will return a 404 error if no results are found...
                return [];
            }
            throw $e;
        }
    }

    /**
     * Créé une société dans la DB dolibarr, et retourne l'id de celle-ci.
     */
    public function createSociety(Organization $organization, bool $client = false): mixed
    {
        $body = [
            'name' => $organization->getName(),
            'client' => $client ? 1 : 2,
            'code_client' => -1,
            'import_key' => 'crm',
            'array_options' => ['options_2iopen_organization_id' => $organization->getId()],
        ];

        /** @var ResponseInterface $response */
        $response = $this->post('/thirdparties', $body);

        return json_decode($response->getContent(), true);
    }

    /**
     * Delete the organization from Dolibarr.
     *
     * @throws \JsonException
     * @throws TransportExceptionInterface
     */
    public function switchSocietyToProspect(int $organizationId): void
    {
        $socId = $this->getSociety($organizationId)['id'];

        $res = $this->put(
            "thirdparties/$socId",
            ['client' => 2],
        );

        if ($res->getStatusCode() !== 200) {
            throw new HttpException($res->getStatusCode(), 'Error while updating the society in Dolibarr');
        }
    }

    /**
     * @return array<string, string|number> The resulting document. Ex :
     *                                      {
     *                                      "filename": "CO2502-0380.pdf",
     *                                      "content-type": "application/pdf",
     *                                      "filesize": 10660,
     *                                      "content": "JVBERi0xLjcKJeLjz9MKNyAwIG9iago8PCAvV...",
     *                                      "encoding": "base64"
     *                                      }
     *
     * @throws \JsonException
     */
    public function downloadBillingDocPdf(string $type, string $docRef): array
    {
        if (!DolibarrDocTypeEnum::tryFrom($type)) {
            throw new \InvalidArgumentException(sprintf('Invalid type "%s" provided. Allowed values are: %s', $type, implode(', ', array_map(fn ($t) => $t->value, DolibarrDocTypeEnum::cases()))));
        }

        $route = 'documents/download?modulepart='.$type.'&original_file='.urlencode("$docRef/$docRef.pdf");

        return $this->getJsonContent($route);
    }

    /**
     * Créé un nouveau contrat Dolibarr
     *
     * @param int $socId
     * @param int $productId L'id du produit dans dolibarr (@see DolibarrUtils::getProductId())
     * @param bool $isNewClient
     * @param int $duration Durée du contrat (en mois)
     * @return int
     * @throws Exception
     */
    public function createContract(
        int  $socId,
        int  $productId,
        bool $isNewClient = false,
        int $duration = 12
    ): int {
        $route = "contracts";
        $date = DatesUtils::new();

        $product = $this->getJsonContent(
            "products/$productId"
        );

        // Evolution (3) ou nouveau client (1)
        $originVente = $isNewClient ? 1 : 3;

        $body = [
            "socid" => $socId,
            "date_contrat" => $date->format('Y-m-d'),
            "commercial_signature_id" => 8,
            "commercial_suivi_id" => 8,
            'statut' => 1,
            'lines' => [
                [
                    'fk_product' => $productId,
                    'label' => $product['label'],
                    'desc' => $product['description'],
                    'qty' => 1,
                    'subprice' => number_format((float)$product['price'],2),
                    'price_base_type' => $product['price_base_type'],
                    'tva_tx' => $product['tva_tx'],
                ]
            ],
            'array_options' => [
                'options_ec_amount' => number_format((float)$product['price'],2),
                'options_ec_duration_months' => $duration,
                'options_ec_signature_date' => $date->format('Y-m-d'),
                'options_ec_effective_date' => $date->format('Y-m-d'),
                'options_ec_tacit_renewal' => 1,
                'options_ec_termination_period_months' => 2,
                'options_ec_billing_due' => 1,
                'options_ec_billing_frequency' => 4,
                'options_ec_billing_begin_period' => 1,
                'options_ec_payment_condition' => 7, // A Livraison
                'options_ec_payment_mode' => 6, // CB (voir table llx_c_paiement)
                'options_ec_account' => 1,
                'options_logicielfact' => 1,
                'options_versionfact' => 2,
                'options_2iopen_origvente' => $originVente,
            ]
        ];

        return (int)$this->post($route, $body)->getContent();
    }

    /**
     * Ajoute une ligne au contrat
     *
     * @param int $contractId
     * @param int $productId
     * @param int $duration Durée du contrat (en jours)
     * @return int
     * @throws Exception
     */
    public function createContractLine(int $contractId, int $productId, int $duration = 12): int {
        $route = "contracts/$contractId/lines";

        $date = DatesUtils::new();
        $endDate = DatesUtils::new()->modify('+'.$duration.' months')->modify('-1 day');

        $product = $this->getJsonContent(
            "products/$productId"
        );

        $body = [
            'fk_product' => $productId,
            'label' => $product['label'],
            'desc' => $product['description'],
            'qty' => 1,
            'subprice' => number_format((float)$product['price'],2),
            'price_base_type' => $product['price_base_type'],
            'tva_tx' => $product['tva_tx'],
            'date_start' => $date->format('Y-m-d'),
            'date_end' => $endDate->format('Y-m-d'),
        ];

        return (int)$this->post($route, $body)->getContent();
    }

    /**
     * Met à jour le produit possédé par la structure dans Dolibarr
     *
     * @param int $socId
     * @param SettingsProductEnum $contractType Produit concerné (@see SettingsProductEnum)
     * @param bool $isTrial
     * @return void
     */
    public function updateSocietyProduct(int $socId, string $productName): void
    {
        $route = "thirdparties/$socId";

        $body = [
            'array_options' => [
                'options_2iopen_software_opentalent' => $productName
            ]
        ];

        $this->put($route, $body);
    }
}
