<?php

namespace onespace\tools\activeApi\components\clients\facetec\base;

use Yii;
use yii\base\Component;
use yii\helpers\ArrayHelper;
use yii\httpclient\Client;
use yii\httpclient\Response;

/**
 * FaceTec API Client Component
 *
 * Handles all interactions with the FaceTec API including:
 * - Session token generation
 * - 3D face enrollment and matching
 * - ID scanning and verification
 * - Face map operations
 *
 * @property string $apiUrl
 * @property string $deviceKey
 * @property string $productionKey
 * @property string $faceScanPublicKey
 * @property string $mobileProductionKey
 * @property array $baseHeaders
 */
class FaceTecClient extends Component {
    public $apiUrl;
    public $deviceKey;
    public $productionKey;
    public $faceScanPublicKey;
    public $mobileProductionKey;
    public $baseHeaders;

    private const ENDPOINT_SESSION_TOKEN = '/session-token';
    private const ENDPOINT_ENROLLMENT_3D = '/enrollment-3d';
    private const ENDPOINT_MATCH_3D_3D = '/match-3d-3d';
    private const ENDPOINT_MATCH_3D_ID = '/match-3d-2d-idscan';
    private const ENDPOINT_FACEMAP_AUDIT = '/facemap-audit-data';
    private const ENDPOINT_ID_SCAN = '/idscan-only';

    private const DEFAULT_MIN_MATCH_LEVEL = 3;

    public function __construct($config = []) {
        $this->initializeConfiguration();
        parent::__construct($config);
    }

    /**
     * Initialize configuration from environment variables
     */
    private function initializeConfiguration(): void {
        $this->apiUrl = $this->getEnvironmentVariable('FACETEC_API_URL');
        $this->deviceKey = $this->getEnvironmentVariable('FACETEC_DEVICE_KEY');
        $this->productionKey = $this->getEnvironmentVariable('FACETEC_PRODUCTION_KEY');
        $this->faceScanPublicKey = $this->getEnvironmentVariable('FACETEC_PUBLIC_KEY');
        $this->baseHeaders = [];
    }

    private function getEnvironmentVariable(string $key): string {
        $value = getenv($key);
        if ($value === false) {
            Yii::warning("Environment variable '{$key}' is not set", __METHOD__);
            return '';
        }
        return $value;
    }

    /**
     * Generate session token for FaceTec operations
     *
     * @param string $userAgent Client user agent string
     * @return array|false API response data or false on failure
     */
    public function getSessionToken(string $userAgent) {
        $headers = $this->buildHeaders(['X-User-Agent' => $userAgent]);
        return $this->makeGetRequest(self::ENDPOINT_SESSION_TOKEN, $headers);
    }

    /**
     * Enroll a 3D face scan
     *
     * @param string $faceScan Base64 encoded face scan data
     * @param string $auditTrailImage Base64 encoded audit trail image
     * @param string $lowQualityAuditTrailImage Base64 encoded low quality audit image
     * @param string $sessionId Session identifier
     * @param string $databaseRefId External database reference ID
     * @param string $userAgent Client user agent string
     * @return array|false API response data or false on failure
     */
    public function enrol3d(
        string $faceScan,
        string $auditTrailImage,
        string $lowQualityAuditTrailImage,
        string $sessionId,
        string $databaseRefId,
        string $userAgent
    ) {
        $payload = [
            'sessionId' => $sessionId,
            'externalDatabaseRefID' => $databaseRefId,
            'faceScan' => $faceScan,
            'auditTrailImage' => $auditTrailImage,
            'lowQualityAuditTrailImage' => $lowQualityAuditTrailImage,
        ];

        $headers = $this->buildHeaders(['X-User-Agent' => $userAgent]);

        return $this->makePostRequest(self::ENDPOINT_ENROLLMENT_3D, $payload, $headers);
    }

    /**
     * Match two 3D face scans
     *
     * @param string $faceScan Base64 encoded face scan data
     * @param string $auditTrailImage Base64 encoded audit trail image
     * @param string $lowQualityAuditTrailImage Base64 encoded low quality audit image
     * @param string $sessionId Session identifier
     * @param string $databaseRefId External database reference ID
     * @param string $userAgent Client user agent string
     * @return array|false API response data or false on failure
     */
    public function match3d3d(
        string $faceScan,
        string $auditTrailImage,
        string $lowQualityAuditTrailImage,
        string $sessionId,
        string $databaseRefId,
        string $userAgent
    ) {
        $payload = [
            'sessionId' => $sessionId,
            'externalDatabaseRefID' => $databaseRefId,
            'faceScan' => $faceScan,
            'auditTrailImage' => $auditTrailImage,
            'lowQualityAuditTrailImage' => $lowQualityAuditTrailImage,
        ];

        $headers = $this->buildHeaders(['X-User-Agent' => $userAgent]);

        return $this->makePostRequest(self::ENDPOINT_MATCH_3D_3D, $payload, $headers);
    }

    /**
     * Match 3D face scan with ID scan
     *
     * @param string $idScan Base64 encoded ID scan data
     * @param string $sessionId Session identifier
     * @param string $databaseRefId External database reference ID
     * @param string $userAgent Client user agent string
     * @param int $minMatchLevel Minimum match level (default: 3)
     * @return array|false API response data or false on failure
     */
    public function match3dIdScan(
        string $idScan,
        string $sessionId,
        string $databaseRefId,
        string $userAgent,
        int $minMatchLevel = self::DEFAULT_MIN_MATCH_LEVEL
    ) {
        $payload = [
            'idScan' => $idScan,
            'sessionId' => $sessionId,
            'externalDatabaseRefID' => $databaseRefId,
            'minMatchLevel' => $minMatchLevel,
        ];

        $headers = $this->buildHeaders(['X-User-Agent' => $userAgent]);

        return $this->makePostRequest(self::ENDPOINT_MATCH_3D_ID, $payload, $headers);
    }

    /**
     * Retrieve face map for a database reference ID
     *
     * @param string $databaseRefId External database reference ID
     * @param string|null $userAgent Optional client user agent string
     * @return array|false API response data or false on failure
     */
    public function getFaceMap(string $databaseRefId, ?string $userAgent = null) {
        $endpoint = self::ENDPOINT_ENROLLMENT_3D . "/{$databaseRefId}";
        $headers = $this->buildHeaders($userAgent ? ['X-User-Agent' => $userAgent] : []);
        return $this->makeGetRequest($endpoint, $headers);
    }

    /**
     * Convert face map to audit image
     *
     * @param string $faceMapBase64 Base64 encoded face map data
     * @return array|false API response data or false on failure
     */
    public function faceMapToAuditImage(string $faceMapBase64) {
        $payload = ['faceMap' => $faceMapBase64];
        $headers = $this->buildHeaders();
        return $this->makePostRequest(self::ENDPOINT_FACEMAP_AUDIT, $payload, $headers);
    }

    /**
     * Scan ID document
     *
     * @param string $idScan Base64 encoded ID scan data
     * @param string $idScanFrontImage Base64 encoded front image
     * @param string $idScanBackImage Base64 encoded back image
     * @param string $userAgent Client user agent string
     * @param string $sessionId Session identifier
     * @return array|false API response data or false on failure
     */
    public function scanId(
        string $idScan,
        string $idScanFrontImage,
        string|null $idScanBackImage,
        string $userAgent,
        string $sessionId
    ) {
        $payload = [
            'idScan' => $idScan,
            'idScanFrontImage' => $idScanFrontImage,
            'idScanBackImage' => $idScanBackImage,
            'sessionId' => $sessionId,
        ];

        $headers = $this->buildHeaders(['X-User-Agent' => $userAgent]);

        return $this->makePostRequest(self::ENDPOINT_ID_SCAN, $payload, $headers);
    }

    /**
     * Build request headers with device key and optional additional headers
     *
     * @param array $additionalHeaders Optional additional headers
     * @return array Complete headers array
     */
    protected function buildHeaders(array $additionalHeaders = []): array {
        return ArrayHelper::merge(
            $this->baseHeaders,
            ['X-Device-Key' => $this->deviceKey],
            $additionalHeaders
        );
    }

    /**
     * Make GET request to API endpoint
     *
     * @param string $endpoint API endpoint
     * @param array $headers Request headers
     * @return array|false API response data or false on failure
     */
    protected function makeGetRequest(string $endpoint, array $headers = []) {
        $client = new Client(['baseUrl' => $this->apiUrl]);
        $response = $client->get($endpoint, null, $headers)
            ->setFormat(Client::FORMAT_JSON)
            ->send();

        return $this->handleResponse($response, $endpoint);
    }

    /**
     * Make POST request to API endpoint
     *
     * @param string $endpoint API endpoint
     * @param array $payload Request payload
     * @param array $headers Request headers
     * @return array|false API response data or false on failure
     */
    protected function makePostRequest(string $endpoint, array $payload, array $headers = []) {
        $client = new Client(['baseUrl' => $this->apiUrl]);
        $response = $client->post($endpoint, $payload, $headers)
            ->setFormat(Client::FORMAT_JSON)
            ->send();

        return $this->handleResponse($response, $endpoint);
    }

    /**
     * Handle API response consistently
     *
     * @param Response $response HTTP response object
     * @param string $endpoint Endpoint that was called (for logging)
     * @return array|false API response data or false on failure
     */
    protected function handleResponse(Response $response, string $endpoint) {

        if ($response->isOk) {
            return $response->data;
        }

        $error = [
            'endpoint' => $endpoint,
            'code' => $response->statusCode,
            'content' => $response->content,
        ];

        Yii::error("FaceTecClientError: " . json_encode($error), __METHOD__);
        return false;
    }
}
