<?php

namespace App\Services;

use Exception;
use Illuminate\Support\Facades\Log;

/**
 * Automação OAuth para Bling via cURL (headless)
 * Permite obter tokens automaticamente sem interação do usuário
 */
class BlingOAuthAutomation
{
    private string $clientId;
    private string $clientSecret;
    private string $redirectUri;
    private string $state;
    private string $email;
    private string $password;
    private string $authUrl;
    private string $tokenUrl;
    private string $cookieFile;

    public function __construct()
    {
        $this->clientId = config('services.bling.client_id');
        $this->clientSecret = config('services.bling.client_secret');
        $this->redirectUri = config('services.bling.redirect_uri');
        $this->state = config('services.bling.state');
        $this->email = config('services.bling.email');
        $this->password = config('services.bling.password');
        $this->authUrl = config('services.bling.auth_url');
        $this->tokenUrl = config('services.bling.token_url');
        $this->cookieFile = storage_path('app/bling_cookies.txt');
    }

    /**
     * Executa o fluxo OAuth completo e retorna tokens
     * 
     * @return array ['access_token' => string, 'refresh_token' => string, 'expires_in' => int]
     * @throws Exception
     */
    public function getTokens(): array
    {
        // Passo 1: Acessar URL de autorização e ser redirecionado para login
        $authorizationUrl = $this->buildAuthorizationUrl();
        $loginPageHtml = $this->accessAuthorizationUrl($authorizationUrl);

        // Verificar se já obtivemos o code diretamente (redirect automático)
        if (str_starts_with($loginPageHtml, 'CODE_IN_URL:')) {
            $authorizationCode = substr($loginPageHtml, 12); // Remove "CODE_IN_URL:"
            Log::info('BlingOAuth: Code obtido diretamente da URL de autorização');
        } else {
            // Passo 2: Extrair CSRF tokens do formulário de login
            $csrfTokens = $this->extractCsrfTokens($loginPageHtml);
            if (!$csrfTokens) {
                throw new Exception('Falha ao extrair CSRF tokens da página de login');
            }

            // Passo 3: Fazer login (POST com credenciais + CSRF)
            $loginAction = $this->extractLoginAction($loginPageHtml);
            $authorizationCode = $this->performLogin($loginAction, $csrfTokens);

            if (!$authorizationCode) {
                throw new Exception('Falha ao obter authorization code após login');
            }
        }

        // Passo 4: Trocar authorization code por tokens
        $tokens = $this->exchangeCodeForTokens($authorizationCode);

        // Limpar arquivo de cookies
        if (file_exists($this->cookieFile)) {
            @unlink($this->cookieFile);
        }

        return $tokens;
    }

    private function buildAuthorizationUrl(): string
    {
        return $this->authUrl . '?' . http_build_query([
            'response_type' => 'code',
            'client_id' => $this->clientId,
            'redirect_uri' => $this->redirectUri,
            'state' => $this->state,
        ]);
    }

    private function accessAuthorizationUrl(string $url): string
    {
        Log::info('BlingOAuth: Acessando URL de autorização', ['url' => $url]);

        // Usar callback para capturar headers de redirect ANTES de seguir para localhost
        $redirectHeaders = [];
        
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_COOKIEJAR => $this->cookieFile,
            CURLOPT_COOKIEFILE => $this->cookieFile,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            CURLOPT_HEADERFUNCTION => function($curl, $header) use (&$redirectHeaders) {
                $len = strlen($header);
                // Capturar Location headers durante redirects
                if (preg_match('/^Location:\s*(.+)$/i', trim($header), $matches)) {
                    $location = trim($matches[1]);
                    $redirectHeaders[] = $location;
                    
                    // Se Location contém o callback com code, capturar
                    if (preg_match('/\/bling\/callback\?code=([^&\s]+)/', $location, $codeMatch)) {
                        // Armazenar o code (será acessado depois)
                        $redirectHeaders['CODE'] = $codeMatch[1];
                    }
                }
                return $len;
            }
        ]);

        $html = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
        $error = curl_error($ch);
        curl_close($ch);

        Log::info('BlingOAuth: Resposta da autorização', [
            'http_code' => $httpCode,
            'effective_url' => $effectiveUrl,
            'redirects_count' => count(array_filter($redirectHeaders, 'is_string')),
            'has_code' => isset($redirectHeaders['CODE']),
        ]);

        // Se capturamos o code nos headers de redirect
        if (isset($redirectHeaders['CODE'])) {
            Log::info('BlingOAuth: Code capturado dos headers de redirect', [
                'code' => substr($redirectHeaders['CODE'], 0, 20) . '...'
            ]);
            return "CODE_IN_URL:" . $redirectHeaders['CODE'];
        }

        // Se foi redirecionado para callback (com code) na URL efetiva
        if (preg_match('/\/bling\/callback\?code=([^&\s]+)/', $effectiveUrl, $matches)) {
            Log::info('BlingOAuth: Code detectado na URL efetiva', ['code' => substr($matches[1], 0, 20) . '...']);
            return "CODE_IN_URL:" . $matches[1];
        }

        // Se chegou aqui e tem HTML, é a página de login
        if ($html && $httpCode === 200) {
            return $html;
        }

        Log::error('BlingOAuth: Falha ao acessar OAuth', [
            'http_code' => $httpCode,
            'error' => $error,
            'html_length' => strlen($html ?? ''),
        ]);
        
        throw new Exception("Falha ao acessar página de autorização OAuth (HTTP $httpCode)" . ($error ? ": $error" : ""));
    }

    private function extractCsrfTokens(string $html): ?array
    {
        $csrfName = null;
        $csrfValue = null;

        // Extrair csrf_name
        if (preg_match('/<input[^>]*name=["\']csrf_name["\'][^>]*value=["\']([^"\']+)["\']/', $html, $matches)) {
            $csrfName = $matches[1];
        } elseif (preg_match('/<input[^>]*value=["\']([^"\']+)["\'][^>]*name=["\']csrf_name["\']/', $html, $matches)) {
            $csrfName = $matches[1];
        }

        // Extrair csrf_value
        if (preg_match('/<input[^>]*name=["\']csrf_value["\'][^>]*value=["\']([^"\']+)["\']/', $html, $matches)) {
            $csrfValue = $matches[1];
        } elseif (preg_match('/<input[^>]*value=["\']([^"\']+)["\'][^>]*name=["\']csrf_value["\']/', $html, $matches)) {
            $csrfValue = $matches[1];
        }

        if (!$csrfName || !$csrfValue) {
            return null;
        }

        return [
            'csrf_name' => $csrfName,
            'csrf_value' => $csrfValue,
        ];
    }

    private function extractLoginAction(string $html): string
    {
        if (preg_match('/<form[^>]*action=["\']([^"\']+)["\']/', $html, $matches)) {
            return html_entity_decode($matches[1]);
        }

        throw new Exception('Não foi possível extrair action do formulário de login');
    }

    private function performLogin(string $loginAction, array $csrfTokens): ?string
    {
        $postData = [
            'login' => $this->email,
            'password' => $this->password,
            'csrf_name' => $csrfTokens['csrf_name'],
            'csrf_value' => $csrfTokens['csrf_value'],
        ];

        Log::info('BlingOAuth: Tentando login', [
            'action' => $loginAction,
            'email' => $this->email,
            'csrf_name' => $csrfTokens['csrf_name'],
        ]);

        $ch = curl_init($loginAction);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($postData),
            CURLOPT_FOLLOWLOCATION => false, // Não seguir redirect automaticamente
            CURLOPT_COOKIEJAR => $this->cookieFile,
            CURLOPT_COOKIEFILE => $this->cookieFile,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            CURLOPT_HEADER => true,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        Log::info('BlingOAuth: Resposta do login', [
            'http_code' => $httpCode,
            'response_length' => strlen($response),
        ]);

        if ($httpCode !== 302) {
            Log::error('BlingOAuth: Login falhou', [
                'http_code' => $httpCode,
                'response' => substr($response, 0, 500),
            ]);
            throw new Exception("Login falhou (HTTP $httpCode)");
        }

        // Extrair Location header
        if (preg_match('/Location:\s*(.+)/i', $response, $matches)) {
            $redirectUrl = trim($matches[1]);
            
            Log::info('BlingOAuth: Redirect após login', ['url' => $redirectUrl]);
            
            // Seguir o redirect para obter o código
            return $this->followRedirectToGetCode($redirectUrl);
        }

        Log::error('BlingOAuth: Location header não encontrado');
        return null;
    }

    private function followRedirectToGetCode(string $url): ?string
    {
        Log::info('BlingOAuth: Seguindo redirect para capturar code', ['url' => $url]);

        // Capturar Location header sem seguir o redirect
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => false, // NÃO seguir redirect para não acessar localhost
            CURLOPT_COOKIEJAR => $this->cookieFile,
            CURLOPT_COOKIEFILE => $this->cookieFile,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            CURLOPT_HEADER => true,
            CURLOPT_NOBODY => false,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        Log::info('BlingOAuth: Resposta do redirect', [
            'http_code' => $httpCode,
            'response_length' => strlen($response),
        ]);

        // Deve ser um redirect (302) com Location header
        if ($httpCode === 302 || $httpCode === 301) {
            // Procurar pelo código no Location header (redirect final para callback)
            if (preg_match('/Location:\s*[^\r\n]*\/bling\/callback\?code=([^&\s\r\n]+)/i', $response, $matches)) {
                $code = trim($matches[1]);
                Log::info('BlingOAuth: Authorization code capturado', ['code' => substr($code, 0, 20) . '...']);
                return $code;
            }
        }

        Log::error('BlingOAuth: Authorization code não encontrado na resposta', [
            'http_code' => $httpCode,
            'response_sample' => substr($response, 0, 800),
        ]);

        return null;
    }

    private function exchangeCodeForTokens(string $code): array
    {
        $postData = [
            'grant_type' => 'authorization_code',
            'code' => $code,
        ];

        $basicAuth = base64_encode($this->clientId . ':' . $this->clientSecret);

        $ch = curl_init($this->tokenUrl);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($postData),
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Accept: application/json',
                'Authorization: Basic ' . $basicAuth,
            ],
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode !== 200) {
            throw new Exception("Falha ao trocar código por tokens (HTTP $httpCode): $response");
        }

        $data = json_decode($response, true);

        if (!isset($data['access_token']) || !isset($data['refresh_token'])) {
            throw new Exception('Resposta de token inválida: ' . $response);
        }

        return [
            'access_token' => $data['access_token'],
            'refresh_token' => $data['refresh_token'],
            'expires_in' => $data['expires_in'] ?? 21600,
        ];
    }
}
