<?php

namespace App\HttpClients\Clients;

use App\HttpClients\DTOs\Hacienda\DocStatusResponse;
use App\HttpClients\DTOs\Hacienda\TokenRequest;
use App\HttpClients\DTOs\Hacienda\TokenResponse;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\UnauthorizedException;

class HaciendaClient
{
    protected bool $isSandboxMode = false;
    protected ?TokenResponse $tokenResponse = null;

    public function useSandboxMode(bool $indicator): void
    {
        $this->isSandboxMode = $indicator;
    }

    public function authenticate(string $username, string $password): void
    {
        $tokenRequest = TokenRequest::from([
            'grant_type' => 'password',
            'client_id' => $this->getClientId(),
            'username' => $username,
            'password' => $password,
        ]);

        $this->requestToken($tokenRequest);
    }

    protected function requestToken(TokenRequest $data): TokenResponse
    {
        try {
            $response = Http::baseUrl($this->getAuthEndpoint())
                ->asForm()
                ->timeout(8)
                ->connectTimeout(5)
                ->withOptions(['verify' => false])
                ->post('/', $data->toArray());

            if ($response->status() === 401) {
                throw new UnauthorizedException('No se ha podido conectar con Hacienda por un tema de credenciales.');
            }

            if ($response->failed()) {
                throw new \RuntimeException('No ha sido posible conectar con Hacienda.');
            }

            $this->tokenResponse = TokenResponse::from($response->json());
        } catch (ConnectionException $e) {
            throw new \RuntimeException('No ha sido posible conectar con Hacienda.');
        }

        return $this->tokenResponse;
    }

    public function sendDocument(array $payload)
    {
        $this->validateToken();

        $secret = trim((string) env('MH_WEBHOOK_SECRET', ''));
        if ($secret === '') {
            throw new \RuntimeException('MH_WEBHOOK_SECRET no está configurado.');
        }

        $callbackUrl = $this->resolveWebhookBaseUrl() . '/api/v1/webhook/mh?token=' . rawurlencode($secret);

        Log::info('Hacienda callback URL preparado', [
            'clave' => $payload['clave'] ?? null,
            'callback_url' => preg_replace('/token=[^&]+/', 'token=***', $callbackUrl),
        ]);

        $body = [
            'clave' => $payload['clave'],
            'fecha' => $payload['fecha'],
            'emisor' => [
                'tipoIdentificacion' => $payload['emisor']['tipo_identificacion'],
                'numeroIdentificacion' => $payload['emisor']['numero_identificacion'],
            ],
            'comprobanteXml' => $payload['comprobanteXml'],
            'callbackUrl' => $callbackUrl,
        ];

        if (!empty($payload['receptor'])) {
            $body['receptor'] = [
                'tipoIdentificacion' => $payload['receptor']['tipo_identificacion'],
                'numeroIdentificacion' => $payload['receptor']['numero_identificacion'],
            ];
        }

        return Http::baseUrl($this->getEndpoint())
            ->timeout(15)
            ->connectTimeout(5)
            ->withOptions(['verify' => false])
            ->withToken($this->getToken())
            ->post('recepcion', $body);
    }

    private function resolveWebhookBaseUrl(): string
    {
        $baseUrl = trim((string) env('MH_WEBHOOK_BASE_URL', ''));

        if ($baseUrl === '') {
            $baseUrl = trim((string) config('app.url', ''));
        }

        $baseUrl = rtrim($baseUrl, '/');

        if ($baseUrl === '') {
            throw new \RuntimeException('No se definió la URL base del webhook. Configure MH_WEBHOOK_BASE_URL o APP_URL.');
        }

        if (!preg_match('#^https?://#i', $baseUrl)) {
            throw new \RuntimeException('La URL base del webhook debe iniciar con http:// o https://.');
        }

        return $baseUrl;
    }

    public function getDocumentReceptionStatus(string $key): DocStatusResponse
    {
        $this->validateToken();

        $response = Http::baseUrl($this->getEndpoint())
            ->withToken($this->getToken())
            ->timeout(10)
            ->connectTimeout(5)
            ->withOptions(['verify' => false])
            ->throw()
            ->get('/recepcion/' . $key);

        return DocStatusResponse::from($response->json());
    }

    public function getTokenResponse(): ?TokenResponse
    {
        return $this->tokenResponse;
    }

    public function getToken(): string
    {
        return $this->tokenResponse->access_token;
    }

    public function isAuthenticated(): bool
    {
        if ($this->tokenResponse === null) {
            return false;
        }

        return !$this->tokenResponse->isExpired();
    }

    private function validateToken(): void
    {
        if ($this->isAuthenticated()) {
            return;
        }

        throw new \RuntimeException('El token de acceso a Hacienda ha expirado o no existe.');
    }

    public function getClientId(): string
    {
        return $this->isSandboxMode ? 'api-stag' : 'api-prod';
    }

    public function getEndpoint(): string
    {
        $environment = $this->isSandboxMode ? 'api-sandbox' : 'api';
        return "https://{$environment}.comprobanteselectronicos.go.cr/recepcion/v1";
    }

    public function getAuthEndpoint(): string
    {
        $environment = $this->isSandboxMode ? 'rut-stag' : 'rut';
        return "https://idp.comprobanteselectronicos.go.cr/auth/realms/{$environment}/protocol/openid-connect/token";
    }
}
