<?php
namespace API\Agena;

use API\FactDematDefault\EFlowFileType;
use API\Partenaire\Exception\WsInvalidResponse;
use API\Partenaire\IConnectionTestable;
use API\Partenaire\IFactDematBranchManager;
use API\Partenaire\IFactDematReceiver;
use API\Partenaire\IPartenaire;
use Entity\Model\Paravrac;
use GuzzleHttp\Utils;
use Helpers\Outil\ArrayHelper;
use Helpers\Securite\Securite;
use Helpers\Session\ParseBase1;
use Override;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\StreamInterface;
use Psr\SimpleCache\CacheInterface;
use Skeleton\Renderer\RendererInterface;

class AgenaPartenaire implements IPartenaire, IFactDematReceiver, IFactDematBranchManager, IConnectionTestable
{

    private ContainerInterface $container;

    private int $id;
    private string $label;
    private string $companyId;

    private AgenaWs $ws;

//_____________________________________________________________________________
// Constructor

    public function __construct(
        ContainerInterface $container,
        int $id,
        string $companyId,
        string $label,
        string $url,
        string $urlAuth,
        string $client,
        string $clientSecret,
        string $target
    ) {
        $this->container = $container;
        $this->id = $id;
        $this->label = $label;
        $this->ws = new AgenaWs(
            ($this->companyId = $companyId),
            $url,
            $urlAuth,
            $client,
            $clientSecret,
            $target,
            $this->container->get(CacheInterface::class)
        );
    }
    
    /**
     * @param array<mixed> $config As given by FactureDematRepo
     */
    public static function getFromConfig(ContainerInterface $container, array $config) :self
    {
        return new AgenaPartenaire(
            $container,
            $config['ID'],
            $config['COMPANY_ID'],
            $config['LABEL'],
            $container->get(Paravrac::class)->isEnvProd()
                ? 'https://a3efa.agena3000.com/api/v1'
                : 'https://a3efa-pa.agena3000.com/api/v1',
            'https://login.agena3000.com/oauth/token',
            $config['CLIENT_ID'],
            $config['CLIENT_SECRET'],
            $container->get(Paravrac::class)->isEnvProd()
                ? 'EFA_Production'
                : 'EFA_ProductionAcceptance'
        );
    }

//_____________________________________________________________________________
// Accessor

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

    public function getLabel(bool $short = false): string
    {
        return $this->label;
    }
    
    public function getCode(): string
    {
        return 'AGENA_'.$this->companyId;
    }

    public function getWs() :AgenaWs
    {
        return $this->ws;
    }

//_____________________________________________________________________________
// IFactDematBranchHandler

    /**
     * @param array<mixed> $branch
     */
    public function createBranch(array $branch) :void
    {
        /** @var \stdClass */
        $created = $this->ws->createBranch($branch);
        
        foreach ([
            'Siren', 'Siret', 'GLN', 'BeKbo', 'VatIntracomm', 'PpfDirectory',
        ] as $field) {
            if (empty($branch[ $field ])) {
                continue;
            }
            $this->ws->addIdentifier(
                $created->Id->Value ?? $created->Id,
                $field,
                $branch[ $field ]
            );
        }
    }
    
    /**
     * @param array<mixed> $branch
     */
    // public function updateBranch(string $id, array $branch) :void
    // {
    //     $this->ws->updateBranch($id, $branch);
    // }

    /**
     * @return array<array<mixed>>
     */
    public function listUser() :array
    {
        $payload = $this->ws->listUser();
        
        return array_map(function ($item) {
            return [
                'id' => $item->Id->Value ?? $item->Id,
                'login' => $item->Login->Value ?? $item->Login,
                'labelFull' => implode(' ', array_filter([
                    $item->FirstName ?? '',
                    $item->LastName ?? '',
                    '(' . ($item->Login->Value ?? $item->Login) . ')',
                ])),
            ];
        }, $payload);
    }

    /**
     * @return array<array<mixed>>
     */
    public function listBranch() :array
    {
        $payload = $this->ws->listBranch();

        return array_map(function ($item) {
            return $this->formatBranch($item);
        }, $payload->Data);
    }

    /**
     * @return array<mixed>
     */
    public function getBranch(string $branchId) :array
    {
        $branch = $this->ws->getBranch($branchId);
        if ($branch == null) {
            throw new \Exception('Invalid branch #'.$branchId);
        }
        return $this->formatBranch($branch);
    }

    /**
     * @param \stdClass $item
     * @return array<mixed>
     */
    private function formatBranch(\stdClass $item): array
    {
        return [
            //'ParentId' => $item->ParentId,
            'Id' => $item->Id->Value ?? $item->Id,
            'BusinessName' => $item->BusinessName->Value ?? $item->BusinessName,
            'RoutingCode' => $item->SimpleIds[0]->Identifier ?? $item->PpfDirectoryIds[0]->RoutingCode ?? '',
            'Siren' => $this->getBranchIdentifierValue($item->IdSimples, 'Siren'),
            'Siret' => $this->getBranchIdentifierValue($item->IdSimples, 'Siret'),
            'BeKBO' => $this->getBranchIdentifierValue($item->IdSimples, 'BeKBO'),
            'VatIntracomm' => $this->getBranchIdentifierValue($item->IdSimples, 'VatIntracomm'),
            'GLN' => $this->getBranchIdentifierValue($item->IdSimples, 'GLN'),
            
        ];
    }
    
    /**
     * @param array<\stdClass> $listIdentifier
     */
    private function getBranchIdentifierValue(array $listIdentifier, string $key) :string
    {
        return ArrayHelper::findFirst($listIdentifier, function ($item) use ($key) {
            return $item->IdType->Value == $key;
        })->IdValue ?? '';
    }


//_____________________________________________________________________________
// IFactDematReceiver

    public function getPPFId(): string
    {
        return '0001';
    }

    public function listNext(array &$context) :array
    {
        $pageSize = 100;
        $listFlow = $this->getWs()->listFlow($context['dateLimit'], $pageSize)->results;
        
        if (count($listFlow) < $pageSize) {
            $context['isLast'] = true;
        }

        // Use last "updatedAt" to get next page (assume result is already sorted bu updatedAt)
        /** @var ?string */
        $dateUpdateMax = array_reduce(
            $listFlow,
            fn($carry, $flow) => max($carry, $flow->updatedAt),
            $context['dateLimit']->format(\DateTime::RFC3339)
        );
        
        $ctx['dateLimit'] = new \DateTime($dateUpdateMax);
        return $listFlow;
    }

    public function getFlowFile(string $id): StreamInterface
    {
        return $this->getWs()->getFlowFile($id, EFlowFileType::Converted);
    }

    public function getCdarFile(string $id): StreamInterface
    {
        return $this->getWs()->getFlowFile($id);
    }
    
    public function getReadableViewFile(object $flow): StreamInterface
    {
        return $this->getWs()->getFlowFile(
            $flow->flowId,
            $flow->flowSyntax == 'Facture-X'
                ? EFlowFileType::Original : EFlowFileType::ReadableView
        );
    }
    
    public function updateStatus(string $flowId, array $cdar) :void
    {
        $this->getWs()->updateStatus(
            $this->container->get(RendererInterface::class),
            $cdar
        );
    }

//_____________________________________________________________________________
// IConnectionTestable

    public function testConnection() :object
    {
        try {
            $this->getWs()->getAuthToken(false);
        } catch (WsInvalidResponse $e) {
            return (object) [
                'status' => 'ERROR',
                'error' => (object) [
                    'type' => 'WS',
                    'message' => (string) $e->getResponse()->getBody(),
                ],
            ];
        } catch (\Exception $e) {
            return (object) [
                'status' => 'ERROR',
                'error' => (object) [
                    'type' => 'EXC',
                    'message' => $e->getMessage(),
                ],
            ];
        }
        return (object) [
            'status' => 'OK',
            'error' => null,
        ];
    }
}
