<?php

namespace onespace\tools\activeApi\components\clients;

use onespace\tools\activeApi\components\clients\base\BaseOneSpaceClient;
use Yii;
use yii\httpclient\Client;
use yii\httpclient\Response;
use yii\web\UploadedFile;

/**
 * Client for the Document Microservice
 *
 * Add the following to `config/params.php`:
 *
 * ```php
 * 'serviceUrls' => [
 *     ...
 *     'document' => getenv('SERVICE_URL_DOCUMENT') ?: 'https://dev.doc-store.1s.co.za/',
 *     ...
 * ],
 * ```
 */
class DocumentClient extends BaseOneSpaceClient {
    public function serverUrl(): string {
        return Yii::$app->params['serviceUrls']['document'];
    }

    /**
     * List the configured document types. (`GET v1/document/types`)
     */
    public function getTypes(): Response {
        $request = $this->getClient()->get($this->serverUrl() . 'v1/document/types');
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Fetch a single document and its extracted data. (`GET v1/document/data`)
     */
    public function getDocument(string $guid): Response {
        $request = $this->getClient()->get($this->serverUrl() . 'v1/document/data?' . http_build_query(['id' => $guid]));
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Fetch a document together with its review form and file links. (`GET v1/document/form`)
     */
    public function getForm(string $guid): Response {
        $request = $this->getClient()->get($this->serverUrl() . 'v1/document/form?' . http_build_query(['id' => $guid]));
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Submit a document review. (`POST v1/document/form`)
     *
     * @param array $data review outcome (`reviewer`, `comment`, `approved`, `expiry`) plus field values
     */
    public function submitReview(string $guid, array $data): Response {
        $request = $this->getClient()->post($this->serverUrl() . 'v1/document/form?' . http_build_query(['id' => $guid]), $data);
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Download a document file via a one-time access token. (`GET v1/document/file`)
     *
     * The raw file bytes are available on the returned response's `content`.
     */
    public function getFile(string $token): Response {
        $request = $this->getClient()->get($this->serverUrl() . 'v1/document/file?' . http_build_query(['token' => $token]));
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * List an entity's documents still awaiting review. (`GET v1/entity/{name}/pending`)
     */
    public function getPending(string $entityName, ?string $entityId = null, ?string $slug = null): Response {
        return $this->entityList($entityName, 'pending', $entityId, $slug);
    }

    /**
     * List an entity's reviewed & approved documents. (`GET v1/entity/{name}/approved`)
     */
    public function getApproved(string $entityName, ?string $entityId = null, ?string $slug = null): Response {
        return $this->entityList($entityName, 'approved', $entityId, $slug);
    }

    /**
     * List an entity's reviewed but rejected documents. (`GET v1/entity/{name}/rejected`)
     */
    public function getRejected(string $entityName, ?string $entityId = null, ?string $slug = null): Response {
        return $this->entityList($entityName, 'rejected', $entityId, $slug);
    }

    /**
     * Check whether an entity has a valid (approved, unexpired) document of a type.
     * (`GET v1/entity/{name}/{id}/has-valid/{slug}`)
     */
    public function hasValid(string $entityName, string $entityId, string $slug): Response {
        $url = $this->serverUrl() . 'v1/entity/' . rawurlencode($entityName) . '/' . rawurlencode($entityId)
            . '/has-valid/' . rawurlencode($slug);
        $request = $this->getClient()->get($url);
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Shared implementation for the pending/approved/rejected entity lists.
     */
    protected function entityList(string $entityName, string $status, ?string $entityId, ?string $slug): Response {
        $query = array_filter(['id' => $entityId, 'slug' => $slug], static fn ($v) => $v !== null);
        $url = $this->serverUrl() . 'v1/entity/' . rawurlencode($entityName) . '/' . $status;
        $request = $this->getClient()->get($url, $query);
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Create an upload record. (`POST v1/upload/data`)
     * @param array $extractedData optional key/value field data
     */
    public function createUpload(string $slug, string $entityName, string $entityId, array $extractedData = []): Response {
        $request = $this->getClient()->post($this->serverUrl() . 'v1/upload/data', [
            'data' => [
                'document' => [
                    'slug' => $slug,
                    'entity' => [
                        'type' => $entityName,
                        'id' => $entityId,
                    ],
                ],
                'extractedData' => $extractedData,
            ],
        ]);
        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Attach file(s) to a previously created upload. (`POST v1/upload/file`)
     *
     * @param UploadedFile[] $files the files to attach
     */
    public function uploadFile(string $token, array $files): Response {
        $request = $this->getClient()->createRequest()
            ->setMethod('POST')
            ->setUrl($this->serverUrl() . 'v1/upload/file?' . http_build_query(['token' => $token]))
            ->setFormat(Client::FORMAT_CURL);

        foreach (array_values($files) as $index => $file) {
            $request->addFile(
                "file[$index]",
                $file->tempName,
                [
                    'fileName' => $file->baseName . '.' . $file->extension,
                    'mimeType' => $file->type,
                ]
            );
        }

        $this->setLastRequest($request);

        return $request->send();
    }

    /**
     * Convenience: create an upload and immediately attach its file(s).
     *
     * Returns the upload/data response if that step fails or its token cannot be
     * read; otherwise returns the upload/file response.
     *
     * @param UploadedFile[] $files
     */
    public function createDocument(
        string $slug,
        string $entityName,
        string $entityId,
        array $files,
        array $extractedData = []
    ): Response {
        $uploadResponse = $this->createUpload($slug, $entityName, $entityId, $extractedData);

        $token = $uploadResponse->isOk ? $this->extractUploadToken((array) $uploadResponse->data) : null;
        if ($token === null) {
            return $uploadResponse;
        }

        return $this->uploadFile($token, $files);
    }

    /**
     * Pull the upload token out of the `uploadUrl` returned by upload/data.
     */
    protected function extractUploadToken(array $upload): ?string {
        $uploadUrl = $upload['uploadUrl'] ?? null;
        if ($uploadUrl === null) {
            return null;
        }
        $query = parse_url($uploadUrl, PHP_URL_QUERY);
        if ($query === null || $query === false) {
            return null;
        }
        parse_str($query, $parts);

        return $parts['token'] ?? null;
    }
}
