<?php

namespace Services;

use Models\UserModel;

class TshieldService
{
    private string $baseUrl;
    private string $login;
    private string $password;
    private string $clientId;
    private string $validationIdPf;
    private string $validationIdPj;
    private int $timeout = 30;
    private ?string $token = null;
    private UserModel $userModel;
    private array $individualFieldMap;
    private string $logFilePath;

    public function __construct()
    {
        $this->baseUrl = rtrim($_ENV['TSHIELD_BASE_URL'] ?? '', '/');
        $this->login = $_ENV['TSHIELD_LOGIN'] ?? '';
        $this->password = $_ENV['TSHIELD_PASSWORD'] ?? '';
        $this->clientId = $_ENV['TSHIELD_CLIENT'] ?? '';
        $this->validationIdPf = $_ENV['TSHIELD_VALIDATION_ID'] ?? '';
        $this->validationIdPj = $_ENV['TSHIELD_VALIDATION_ID_CNPJ'] ?? '';

        if ($this->baseUrl === ''
            || $this->login === ''
            || $this->password === ''
            || $this->clientId === ''
            || $this->validationIdPf === ''
            || $this->validationIdPj === ''
        ) {
            throw new \RuntimeException(
                'Missing TShield configuration. Required envs: TSHIELD_BASE_URL, TSHIELD_LOGIN, ' .
                'TSHIELD_PASSWORD, TSHIELD_CLIENT, TSHIELD_VALIDATION_ID, TSHIELD_VALIDATION_ID_CNPJ.'
            );
        }

        $this->userModel = new UserModel();
        $this->logFilePath = $this->resolveLogFilePath($_ENV['TSHIELD_LOG_FILE'] ?? null);
        $this->individualFieldMap = [
            'name' => (int)($_ENV['TSHIELD_FIELD_NAME_ID'] ?? 16175),
            'document' => (int)($_ENV['TSHIELD_FIELD_DOCUMENT_ID'] ?? 16176),
            'birthdate' => (int)($_ENV['TSHIELD_FIELD_BIRTHDATE_ID'] ?? 16177),
            'phone' => (int)($_ENV['TSHIELD_FIELD_PHONE_ID'] ?? 16178),
            'email' => (int)($_ENV['TSHIELD_FIELD_EMAIL_ID'] ?? 16179),
        ];
    }

    /**
     * Cria análise e link de KYC para pessoa física.
     *
     * @param int $userId           Usuário local que receberá o número externo.
     * @param array $analysisPayload Payload aceito por POST /api/query.
     * @param array $linkPayload     Payload adicional para POST /api/query/external/token (por exemplo, redirectUrl).
     *
     * @return array{
     *     number: string,
     *     link: ?string,
     *     analysis: array<mixed>,
     *     link_response: array<mixed>
     * }
     */
    public function generateIndividualLink(int $userId, array $analysisPayload, array $linkPayload = []): array
    {
        $linkPayload = $this->buildIndividualLinkPayload($analysisPayload, $linkPayload);
        $linkResponse = $this->createLink($this->validationIdPf, $linkPayload);
        $analysisNumber = $linkResponse['number']
            ?? ($linkResponse['data']['number'] ?? null);

        if (!$analysisNumber) {
            $analysisNumber = $linkResponse['token']
                ?? ($linkResponse['data']['token'] ?? null);
        }

        if (!$analysisNumber) {
            throw new \RuntimeException(sprintf(
                'Unable to extract analysis number from PF link response: %s',
                json_encode($linkResponse, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
            ));
        }

        $this->persistExternalId($userId, (string)$analysisNumber);
        $link = $linkResponse['link']
            ?? $linkResponse['url']
            ?? ($linkResponse['data']['link'] ?? ($linkResponse['data']['url'] ?? null));

        return [
            'number' => (string)$analysisNumber,
            'link' => $link,
            'analysis' => $linkPayload,
            'link_response' => $linkResponse,
        ];
    }

    /**
     * Cria análise e link de KYC para pessoa jurídica.
     *
     * @param int   $userId
     * @param array $analysisPayload Payload aceito por POST /api/query/company.
     * @param array $linkPayload
     *
     * @return array{
     *     number: string,
     *     link: ?string,
     *     analysis: array<mixed>,
     *     link_response: array<mixed>
     * }
     */
    public function generateCompanyLink(int $userId, array $analysisPayload, array $linkPayload = []): array
    {
        $analysisPayload = $this->injectDefaults($analysisPayload, $this->validationIdPj);
        $analysisNumber = $this->createAnalysis('/api/query/company', $analysisPayload);
        $this->persistExternalId($userId, $analysisNumber);

        return [
            'number' => $analysisNumber,
            'link' => null,
            'analysis' => $analysisPayload,
            'link_response' => [
                'data' => [
                    'client_validation_id' => $this->validationIdPj,
                    'number' => $analysisNumber,
                ],
            ],
        ];
    }

    private function createAnalysis(string $path, array $payload): string
    {
        $response = $this->request('POST', $path, $payload);

        $number = $response['data']['number']
            ?? $response['number']
            ?? $response['data']['client_validation_id']
            ?? $response['client_validation_id']
            ?? null;

        if (!$number) {
            throw new \RuntimeException(sprintf(
                'Unable to extract analysis number from %s response: %s',
                $path,
                json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
            ));
        }

        return (string)$number;
    }

    private function createLink(string $validationId, array $payload): array
    {
        $body = array_merge(['client_validation_id' => $validationId], $payload);
        $response = $this->request('POST', '/api/query/external/token', $body);

        if (
            !isset($response['link']) &&
            !isset($response['url']) &&
            !isset($response['data']['link']) &&
            !isset($response['data']['url'])
        ) {
            throw new \RuntimeException(sprintf(
                'Invalid link response from TShield: %s',
                json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
            ));
        }

        return $response;
    }

    private function persistExternalId(int $userId, string $externalId): void
    {
        if (!$this->userModel->updateKycExternalId($userId, $externalId)) {
            throw new \RuntimeException('Unable to persist KYC external id for user ' . $userId);
        }
    }

    private function ensureToken(): void
    {
        if ($this->token !== null) {
            return;
        }

        $loginPayload = [
            'login' => $this->login,
            'password' => $this->password,
            'client' => $this->clientId,
        ];

        $loginResponse = $this->request('POST', '/api/login', $loginPayload, false);

        $token = $loginResponse['token']
            ?? ($loginResponse['data']['token'] ?? ($loginResponse['access_token'] ?? null))
            ?? ($loginResponse['user']['token'] ?? null);
        if ($token === null) {
            throw new \RuntimeException('TShield login did not return a token.');
        }

        $this->token = (string) $token;
    }

    private function request(string $method, string $path, array $payload = [], bool $requiresAuth = true): array
    {
        if ($requiresAuth) {
            $this->ensureToken();
        }

        $url = $this->baseUrl . $path;
        $ch = curl_init($url);

        $headers = ['Content-Type: application/json'];
        if ($requiresAuth && $this->token !== null) {
            $headers[] = 'Authorization: Bearer ' . $this->token;
        }

        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        if (!empty($payload)) {
            $encoded = json_encode($payload);
            if ($encoded === false) {
                throw new \RuntimeException('Unable to encode payload to JSON.');
            }
            curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
        }

        $this->logRequest($method, $url, $payload);

        $responseBody = curl_exec($ch);
        $curlErrNo = curl_errno($ch);
        $curlErr = curl_error($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $this->logToFile($method, $url, $httpCode, $payload, $responseBody);

        if ($curlErrNo !== 0) {
            throw new \RuntimeException(sprintf('cURL error while calling %s: %s (%d)', $url, $curlErr, $curlErrNo));
        }

        if ($responseBody === false || $responseBody === '') {
            throw new \RuntimeException(sprintf('Empty response from TShield endpoint %s', $url));
        }

        $decoded = json_decode($responseBody, true);
        if ($decoded === null) {
            throw new \RuntimeException(sprintf('Invalid JSON response from %s: %s', $url, $responseBody));
        }

        if ($httpCode >= 400) {
            $message = $decoded['message'] ?? $decoded['error'] ?? 'TShield request failed';
            throw new \RuntimeException(sprintf(
                '%s (HTTP %d) payload=%s',
                $message,
                $httpCode,
                json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
            ));
        }

        return $decoded;
    }

    private function resolveLogFilePath(?string $path): string
    {
        $path = trim((string)$path);

        if ($path === '') {
            $path = 'storage/logs/tshield_response.txt';
        }

        if (!preg_match('/^(?:[A-Za-z]:\\\\|\\/)/', $path)) {
            $path = rtrim(dirname(__DIR__), '/\\') . DIRECTORY_SEPARATOR . $path;
        }

        $dir = dirname($path);
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }

        return $path;
    }

    private function logToFile(string $method, string $url, int $httpCode, array $payload, $responseBody): void
    {
        $ts = (new \DateTimeImmutable())->format('c');
        $payloadEncoded = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        $responseText = is_string($responseBody) ? $responseBody : '';
        $entry = "[$ts] $method $url HTTP=$httpCode\n" .
            "payload=$payloadEncoded\n" .
            "response=$responseText\n" .
            "----\n";

        try {
            @file_put_contents($this->logFilePath, $entry, FILE_APPEND | LOCK_EX);
        } catch (\Throwable $e) {
        }
    }

    private function logRequest(string $method, string $url, array $payload): void
    {
        $encoded = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        error_log(sprintf('[TShield] %s %s payload=%s', $method, $url, $encoded));
    }

    private function buildIndividualLinkPayload(array $analysisPayload, array $linkPayload): array
    {
        $payload = $linkPayload;
        if (!isset($payload['token_send'])) {
            $payload['token_send'] = [];
        }

        $defaultAdditional = [];
        if (isset($analysisPayload['birthdate'])) {
            $analysisPayload['birthdate'] = $this->normalizeBirthdate($analysisPayload['birthdate']);
        }
        $fieldValues = [
            'name' => $analysisPayload['name'] ?? null,
            'document' => $analysisPayload['document'] ?? null,
            'birthdate' => $analysisPayload['birthdate'] ?? null,
            'phone' => $analysisPayload['phone'] ?? null,
            'email' => $analysisPayload['email'] ?? null,
        ];

        foreach ($fieldValues as $key => $value) {
            if (is_array($value)) {
                $value = $value['number'] ?? reset($value) ?? null;
            }

            if (!$value) {
                continue;
            }
            $fieldId = $this->individualFieldMap[$key] ?? null;
            if (!$fieldId) {
                continue;
            }
            $defaultAdditional[] = [
                'client_validation_additional_field_id' => $fieldId,
                'value' => $value,
                'file_field_type' => null,
                'values' => [$value],
            ];
        }

        if (!isset($payload['additional_fields']) || !is_array($payload['additional_fields'])) {
            $payload['additional_fields'] = $defaultAdditional;
        } elseif (!empty($defaultAdditional)) {
            $payload['additional_fields'] = array_merge($defaultAdditional, $payload['additional_fields']);
        }

        if (empty($payload['company_id']) && isset($_ENV['TSHIELD_COMPANY_ID'])) {
            $payload['company_id'] = $_ENV['TSHIELD_COMPANY_ID'];
        }

        return $payload;
    }

    private function normalizeBirthdate($value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (is_numeric($value)) {
            $numeric = (string)$value;
            $timestamp = (int)$value;

            if ($timestamp > 0) {
                if (strlen($numeric) === 8) {
                    $formatted = \DateTimeImmutable::createFromFormat('Ymd', $numeric);
                    if ($formatted) {
                        return $formatted->format('Y-m-d');
                    }
                }

                if (strlen($numeric) >= 13) {
                    $timestamp = (int) floor($timestamp / 1000);
                }

                if ($timestamp >= 1000000000) {
                    $dt = (new \DateTimeImmutable())->setTimestamp($timestamp);
                    return $dt->format('Y-m-d');
                }
            }
        }

        if (is_string($value)) {
            $value = trim($value);

            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
                return $value;
            }

            if (preg_match('/^\d{8}$/', $value)) {
                $formatted = \DateTimeImmutable::createFromFormat('Ymd', $value);
                if ($formatted) {
                    return $formatted->format('Y-m-d');
                }
            }

            foreach (['d/m/Y', 'd-m-Y', 'Y/m/d', 'Y.m.d'] as $fmt) {
                $formatted = \DateTimeImmutable::createFromFormat($fmt, $value);
                if ($formatted) {
                    return $formatted->format('Y-m-d');
                }
            }
        }

        return null;
    }

    private function injectDefaults(array $payload, string $validationId): array
    {
        if (empty($payload['client_validation_id'])) {
            $payload['client_validation_id'] = $validationId;
        }

        return $payload;
    }
}
