<?php

namespace App\Services;

use App\Models\BlingToken;
use App\Models\IntegrationLog;
use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;

class BlingClient
{
    protected HttpClient $http;

    public function __construct(?HttpClient $http = null)
    {
        $this->http = $http ?: new HttpClient([
            'base_uri' => rtrim((string) env('BLING_API_BASE', 'https://api.bling.com.br/Api/v3'), '/') . '/',
            'timeout'  => 30,
            'headers'  => [
                'Accept' => 'application/json',
            ],
            'verify' => false, // Desabilitar verificação SSL apenas para desenvolvimento
        ]);
    }

    public function buildAuthUrl(): string
    {
        $params = http_build_query([
            'response_type' => 'code',
            'client_id' => env('BLING_CLIENT_ID'),
            'redirect_uri' => env('BLING_REDIRECT_URI'),
            'scope' => env('BLING_SCOPE', ''),
            'state' => env('BLING_OAUTH_STATE', 'state'),
        ]);

    $authUrl = rtrim((string) env('BLING_AUTH_URL', 'https://bling.com.br/Api/v3/oauth/authorize'), '?');
        return $authUrl . '?' . $params;
    }

    public function authorizeWithCode(string $code): BlingToken
    {
        $resp = (new HttpClient([ 'timeout' => 30 ]))->post(
            env('BLING_TOKEN_URL', 'https://bling.com.br/Api/v3/oauth/token'),
            [
                'form_params' => [
                    'grant_type' => 'authorization_code',
                    'code' => $code,
                    'client_id' => env('BLING_CLIENT_ID'),
                    'client_secret' => env('BLING_CLIENT_SECRET'),
                    'redirect_uri' => env('BLING_REDIRECT_URI'),
                ],
            ]
        );

        $data = json_decode((string) $resp->getBody(), true) ?: [];
        return $this->persistTokenFromPayload($data);
    }

    public function getAccessToken(): string
    {
        $latest = BlingToken::query()->latest('id')->first();
        if ($latest && !$latest->isExpired()) {
            return $latest->access_token;
        }

        if (!$latest) {
            // Tentar obter tokens automaticamente via OAuth headless
            try {
                $automation = new BlingOAuthAutomation();
                $tokens = $automation->getTokens();
                $new = $this->persistTokenFromPayload($tokens);
                IntegrationLog::write('bling_token_auto_obtained', ['token_id' => $new->id]);
                return $new->access_token;
            } catch (\Exception $e) {
                IntegrationLog::write('bling_token_auto_failed', ['error' => $e->getMessage()]);
                throw new \RuntimeException('Nenhum token do Bling encontrado e automação OAuth falhou: ' . $e->getMessage());
            }
        }

        // refresh
        try {
            $resp = (new HttpClient([ 'timeout' => 30 ]))->post(
                env('BLING_TOKEN_URL', 'https://bling.com.br/Api/v3/oauth/token'),
                [
                    'form_params' => [
                        'grant_type' => 'refresh_token',
                        'refresh_token' => $latest->refresh_token,
                        'client_id' => env('BLING_CLIENT_ID'),
                        'client_secret' => env('BLING_CLIENT_SECRET'),
                    ],
                ]
            );

            $data = json_decode((string) $resp->getBody(), true) ?: [];
            $new = $this->persistTokenFromPayload($data);
            IntegrationLog::write('bling_token_refreshed', ['token_id' => $new->id]);
            return $new->access_token;
        } catch (\Exception $e) {
            // Se refresh falhar, tentar obter novo token via automação
            IntegrationLog::write('bling_token_refresh_failed', ['error' => $e->getMessage()]);
            try {
                $automation = new BlingOAuthAutomation();
                $tokens = $automation->getTokens();
                $new = $this->persistTokenFromPayload($tokens);
                IntegrationLog::write('bling_token_auto_obtained_after_refresh_fail', ['token_id' => $new->id]);
                return $new->access_token;
            } catch (\Exception $autoE) {
                IntegrationLog::write('bling_token_all_methods_failed', ['error' => $autoE->getMessage()]);
                throw new \RuntimeException('Falha ao renovar token via refresh e via automação OAuth');
            }
        }
    }

    protected function persistTokenFromPayload(array $data): BlingToken
    {
        $access = Arr::get($data, 'access_token');
        $refresh = Arr::get($data, 'refresh_token');
        $expiresIn = (int) Arr::get($data, 'expires_in', 3600);

        if (!$access || !$refresh) {
            IntegrationLog::write('bling_token_invalid_payload', ['payload' => $data]);
            throw new \RuntimeException('Payload de token inválido');
        }

        return BlingToken::create([
            'access_token' => $access,
            'refresh_token' => $refresh,
            'expires_at' => Carbon::now()->addSeconds($expiresIn - 30), // pequena margem
        ]);
    }

    /**
     * Faz requisição à API do Bling com renovação de token e retry exponencial em 429.
     */
    public function apiRequest(string $method, string $uri, array $options = [])
    {
        $attempts = 0;
        $maxAttempts = 3;
        $backoff = 1.0; // segundos

        do {
            $attempts++;
            try {
                $token = $this->getAccessToken();
                $options['headers']['Authorization'] = 'Bearer ' . $token;
                $response = $this->http->request($method, ltrim($uri, '/'), $options);
                return json_decode((string) $response->getBody(), true);
            } catch (RequestException $e) {
                $code = $e->getResponse() ? $e->getResponse()->getStatusCode() : 0;
                if ($code === 401 && $attempts <= $maxAttempts) {
                    // força refresh e tenta novamente
                    $latest = BlingToken::query()->latest('id')->first();
                    if ($latest) {
                        // invalida forçando expiração
                        $latest->expires_at = Carbon::now()->subMinute();
                        $latest->save();
                    }
                    continue;
                }
                if ($code === 429 && $attempts < $maxAttempts) {
                    IntegrationLog::write('bling_rate_limited', ['attempt' => $attempts]);
                    usleep((int) ($backoff * 1_000_000));
                    $backoff *= 2; // backoff exponencial
                    continue;
                }
                IntegrationLog::write('bling_request_failed', [
                    'method' => $method,
                    'uri' => $uri,
                    'attempt' => $attempts,
                    'error' => $e->getMessage(),
                    'status' => $code,
                ]);
                throw $e;
            }
        } while ($attempts < $maxAttempts);
    }
}
