<?php

namespace App\Modules\ThreeCX\Services;

use App\Modules\ThreeCX\Models\ThreeCXToken;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use App\Models\Setting;
use Carbon\Carbon;
use Exception;

/**
 * 3CX OAuth2 Authentication Service
 * 
 * Handles OAuth2 flow, token management, and authentication
 * Based on the official 3CX Call Control API v20+ specification
 */
class ThreeCXAuthService
{
    protected $baseUrl;
    protected $clientId;
    protected $clientSecret;

    public function __construct()
    {
        // Read from database settings (admin interface)
        $this->baseUrl = Setting::get('integrations.threecx.base_url', '');
        $this->clientId = Setting::get('integrations.threecx.client_id', '');
        $this->clientSecret = Setting::get('integrations.threecx.client_secret', '');
    }

    /**
     * Get OAuth2 token using client credentials grant
     * This is the primary method for machine-to-machine authentication with 3CX
     * Since client_credentials doesn't provide refresh tokens, we get a new token when needed
     */
    public function getToken(): string
    {
        try {
            // Check if we have a valid (non-expired) token in cache
            $cacheKey = 'threecx_oauth_token';
            $cachedToken = Cache::get($cacheKey);
            
            if ($cachedToken && isset($cachedToken['expires_at']) && Carbon::parse($cachedToken['expires_at'])->isFuture()) {
                // Only log token usage in debug mode to reduce noise
                Log::debug('[3CX] Using cached OAuth2 token', [
                    'expires_at' => $cachedToken['expires_at']
                ]);
                return $cachedToken['access_token'];
            }

            // No valid cached token, get a new one
            return $this->createNewToken();
            
        } catch (Exception $e) {
            Log::error('[3CX] Failed to get token', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            throw $e;
        }
    }

    /**
     * Create a new OAuth2 token using client credentials
     */
    protected function createNewToken(): string
    {
        // First, delete any existing system tokens to avoid duplicates
        ThreeCXToken::whereNull('user_id')->delete();
        // Validate credentials before making request
        if (empty($this->clientId) || empty($this->clientSecret)) {
            Log::error('[3CX] OAuth2 credentials not configured', [
                'client_id_empty' => empty($this->clientId),
                'client_secret_empty' => empty($this->clientSecret)
            ]);
            throw new Exception('3CX OAuth2 credentials not configured. Please set Client ID and Client Secret in Admin Settings.');
        }

        Log::info('[3CX] Requesting new OAuth2 token');

        $tokenUrl = rtrim($this->baseUrl, '/') . '/connect/token';
        
        $response = Http::asForm()
            ->withOptions(['verify' => false]) // For self-signed certificates
            ->timeout(Setting::get('integrations.threecx.timeout', 30))
            ->post($tokenUrl, [
                'grant_type' => 'client_credentials',
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret,
                'scope' => 'callcontrolapi' // Scope for Call Control API
            ]);

        if (!$response->successful()) {
            $errorMessage = $response->body() ?: 'No error details provided';
            
            Log::error('[3CX] OAuth2 token request failed', [
                'status' => $response->status(),
                'body' => $errorMessage,
                'url' => $tokenUrl,
                'client_id' => $this->clientId // Log client ID for debugging (not secret)
            ]);
            
            // Provide more specific error messages
            if ($response->status() === 400) {
                throw new Exception("Invalid OAuth2 request. Please check your Client ID and Client Secret.");
            } elseif ($response->status() === 401) {
                throw new Exception("OAuth2 authentication failed. Invalid Client ID or Client Secret.");
            } else {
                throw new Exception("Failed to obtain 3CX access token (Status: {$response->status()}): {$errorMessage}");
            }
        }

        $tokenData = $response->json();
        
        // Log the token response for debugging
        Log::info('[3CX] OAuth2 token response', [
            'has_access_token' => isset($tokenData['access_token']),
            'has_refresh_token' => isset($tokenData['refresh_token']),
            'expires_in' => $tokenData['expires_in'] ?? 'not provided',
            'token_type' => $tokenData['token_type'] ?? 'not provided'
        ]);
        
        // Cache the token (no need to store in DB for client_credentials)
        $expiresAt = Carbon::now()->addSeconds($tokenData['expires_in'] ?? 60);
        
        // Store in cache with a slight buffer (expire 5 seconds early to be safe)
        $cacheKey = 'threecx_oauth_token';
        $cacheData = [
            'access_token' => $tokenData['access_token'],
            'expires_at' => $expiresAt->toDateTimeString(),
            'token_type' => $tokenData['token_type'] ?? 'Bearer'
        ];
        
        Cache::put($cacheKey, $cacheData, $expiresAt->subSeconds(5));

        Log::info('[3CX] New OAuth2 token cached successfully', [
            'expires_at' => $expiresAt->toDateTimeString(),
            'expires_in_seconds' => $tokenData['expires_in'] ?? 60
        ]);

        return $tokenData['access_token'];
    }

    /**
     * Get valid access token from cache
     * @deprecated Use getToken() instead - kept for backward compatibility
     */
    public function getValidToken($userId = null, $forceRefresh = false)
    {
        // For client_credentials flow, we don't use database tokens
        // This method is kept for backward compatibility but just returns null
        return null;
    }

    /**
     * Refresh access token
     * For client_credentials flow, this just gets a new token
     * @deprecated Use getToken() instead
     */
    public function refreshToken($token = null)
    {
        // For client_credentials flow, we don't refresh - we get a new token
        Log::info('[3CX] Client credentials flow does not use refresh tokens, getting new token');
        
        // Clear cache to force new token
        Cache::forget('threecx_oauth_token');
        
        // Get new token
        $this->getToken();
    }

    /**
     * Test OAuth2 connection
     */
    public function testConnection(): array
    {
        try {
            Log::info("[3CX] Testing OAuth2 connection", [
                'base_url' => $this->baseUrl,
                'client_id_provided' => !empty($this->clientId),
                'client_secret_provided' => !empty($this->clientSecret)
            ]);
            
            if (empty($this->baseUrl) || empty($this->clientId) || empty($this->clientSecret)) {
                return [
                    'success' => false,
                    'message' => '3CX OAuth2 configuration incomplete. Please set base URL, client ID and client secret.'
                ];
            }

            // Try to get a token
            $token = $this->getToken();
            
            // Test the token with a GET request to call control API
            // This tests if we can access the call control API without making an actual call
            $testExtension = \App\Modules\ThreeCX\Services\ThreeCXExtensionResolver::getExtensionForApiCall();
            $testUrl = rtrim($this->baseUrl, '/') . '/callcontrol/' . $testExtension;
            
            $response = Http::withToken($token)
                ->withOptions(['verify' => false])
                ->timeout(Setting::get('integrations.threecx.timeout', 30))
                ->accept('application/json')
                ->get($testUrl);

            if ($response->successful()) {
                Log::info("[3CX] OAuth2 connection test successful");
                
                return [
                    'success' => true,
                    'message' => 'Successfully connected to 3CX using OAuth2!'
                ];
            } else {
                Log::error("[3CX] OAuth2 connection test failed", [
                    'status' => $response->status(),
                    'body' => $response->body()
                ]);
                
                return [
                    'success' => false,
                    'message' => 'OAuth2 token obtained but API test failed. Status: ' . $response->status()
                ];
            }
            
        } catch (Exception $e) {
            Log::error("[3CX] OAuth2 connection test failed with exception", [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            return [
                'success' => false,
                'message' => 'Connection failed: ' . $e->getMessage()
            ];
        }
    }

    /**
     * Make HTTP request to 3CX Call Control API with OAuth2
     */
    public function makeApiRequest(string $method, string $endpoint, array $data = []): array
    {
        try {
            // Get valid OAuth2 token
            $token = $this->getToken();
            
            // Replace {extension} placeholder with the extension from data
            if (isset($data['from'])) {
                $endpoint = str_replace('{extension}', $data['from'], $endpoint);
            }
            
            // Build the full URL
            $url = rtrim($this->baseUrl, '/') . '/' . ltrim($endpoint, '/');
            
            Log::info("[3CX] Making OAuth2 API request", [
                'method' => $method,
                'url' => $url,
                'endpoint' => $endpoint
            ]);
            
            $timeout = Setting::get('integrations.threecx.timeout', 30);
            
            // Make request with OAuth2 Bearer token
            $request = Http::withToken($token)
                ->withOptions(['verify' => false])
                ->timeout($timeout);
                
            // Make the actual request based on method
            if ($method === 'GET') {
                $response = $request->get($url, $data);
            } else {
                $response = $request->$method($url, $data);
            }

            Log::info("[3CX] OAuth2 API response", [
                'method' => $method,
                'endpoint' => $endpoint,
                'status' => $response->status(),
                'successful' => $response->successful()
            ]);

            return [
                'success' => $response->successful(),
                'status' => $response->status(),
                'data' => $response->json(),
                'body' => $response->body()
            ];
            
        } catch (Exception $e) {
            Log::error("[3CX] OAuth2 API request failed", [
                'method' => $method,
                'endpoint' => $endpoint,
                'error' => $e->getMessage()
            ]);
            
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Get connection status
     */
    public function getConnectionStatus($userId = null)
    {
        $cachedToken = Cache::get('threecx_oauth_token');
        
        if (!$cachedToken || !isset($cachedToken['expires_at']) || Carbon::parse($cachedToken['expires_at'])->isPast()) {
            return [
                'connected' => false,
                'message' => 'Not connected to 3CX'
            ];
        }
        
        return [
            'connected' => true,
            'instance_url' => $this->baseUrl,
            'expires_at' => $cachedToken['expires_at'],
            'token_type' => $cachedToken['token_type'] ?? 'Bearer'
        ];
    }

    /**
     * Disconnect from 3CX (clear cached tokens)
     */
    public function disconnect($userId = null)
    {
        // For client_credentials, we just clear the cache
        Cache::forget('threecx_oauth_token');
        
        Log::info('[3CX] Disconnected - cache cleared');
    }

    /**
     * Revoke token at 3CX
     * @deprecated Not needed for client_credentials flow
     */
    protected function revokeTokenAt3CX($token)
    {
        // Client credentials tokens don't need revocation
        // They expire naturally after short duration
    }
}