<?php

namespace App\Services;

use App\Models\OAuthToken;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use Exception;

/**
 * Unified OAuth Token Management Service
 * 
 * Handles OAuth tokens for all integrations (Xero, Twilio, Google, etc.)
 */
class OAuthTokenService
{
    /**
     * Store OAuth token for a provider
     */
    public function storeToken(array $data): OAuthToken
    {
        $tokenData = [
            'provider' => $data['provider'],
            'provider_type' => $data['provider_type'] ?? 'oauth2',
            'scope' => $data['scope'] ?? 'system',
            'user_id' => $data['user_id'] ?? null,
            'tenant_id' => $data['tenant_id'] ?? null,
            'access_token' => $data['access_token'],
            'refresh_token' => $data['refresh_token'] ?? null,
            'expires_at' => isset($data['expires_in']) ? 
                Carbon::now()->addSeconds($data['expires_in']) : 
                ($data['expires_at'] ?? null),
            'scopes' => $data['scopes'] ?? null,
            'provider_data' => $data['provider_data'] ?? null,
            'token_type' => $data['token_type'] ?? 'Bearer',
            'status' => 'active',
            'connected_at' => Carbon::now(),
            'last_used_at' => Carbon::now()
        ];

        return OAuthToken::updateOrCreate(
            [
                'provider' => $tokenData['provider'],
                'scope' => $tokenData['scope'],
                'user_id' => $tokenData['user_id'],
                'tenant_id' => $tokenData['tenant_id']
            ],
            $tokenData
        );
    }

    /**
     * Get valid token for a provider
     */
    public function getValidToken(string $provider, array $options = []): ?OAuthToken
    {
        $query = OAuthToken::where('provider', $provider)
            ->where('status', 'active');

        // Filter by scope (system, user, tenant)
        if (isset($options['scope'])) {
            $query->where('scope', $options['scope']);
        }

        if (isset($options['user_id'])) {
            $query->where('user_id', $options['user_id']);
        }

        if (isset($options['tenant_id'])) {
            $query->where('tenant_id', $options['tenant_id']);
        }

        // Prioritize system tokens if no specific scope requested
        if (!isset($options['scope'])) {
            $query->orderByRaw("CASE WHEN scope = 'system' THEN 0 ELSE 1 END");
        }

        $token = $query->orderBy('connected_at', 'desc')->first();

        if (!$token) {
            return null;
        }

        // Check if token is expired and try to refresh
        if ($this->isTokenExpired($token)) {
            if ($token->refresh_token && $this->refreshToken($token)) {
                $token->refresh();
                return $token;
            } else {
                $token->update(['status' => 'expired']);
                return null;
            }
        }

        // Update last used timestamp
        $token->update(['last_used_at' => Carbon::now()]);

        return $token;
    }

    /**
     * Check if token is expired
     */
    public function isTokenExpired(OAuthToken $token): bool
    {
        if (!$token->expires_at) {
            return false; // No expiry set, assume it doesn't expire
        }

        // Consider token expired if it expires within 5 minutes
        return $token->expires_at->subMinutes(5)->isPast();
    }

    /**
     * Refresh token using provider-specific logic
     */
    public function refreshToken(OAuthToken $token): bool
    {
        try {
            switch ($token->provider) {
                case 'xero':
                    return $this->refreshXeroToken($token);
                case 'google':
                    return $this->refreshGoogleToken($token);
                case 'microsoft':
                    return $this->refreshMicrosoftToken($token);
                default:
                    Log::warning("Token refresh not implemented for provider: {$token->provider}");
                    return false;
            }
        } catch (Exception $e) {
            Log::error("Failed to refresh {$token->provider} token: " . $e->getMessage());
            return false;
        }
    }

    /**
     * Refresh Xero token
     */
    private function refreshXeroToken(OAuthToken $token): bool
    {
        $xeroAuthService = app(\App\Modules\Xero\Services\XeroAuthService::class);
        
        try {
            // Create a temporary token object for the Xero service
            $tempToken = new \App\Modules\Xero\Models\XeroToken([
                'access_token' => $token->access_token,
                'refresh_token' => $token->refresh_token,
                'expires_at' => $token->expires_at,
                'tenant_id' => $token->tenant_id
            ]);

            $xeroAuthService->refreshToken($tempToken);

            // Update the unified token with new data
            $token->update([
                'access_token' => $tempToken->access_token,
                'refresh_token' => $tempToken->refresh_token,
                'expires_at' => $tempToken->expires_at,
                'last_used_at' => Carbon::now()
            ]);

            return true;
        } catch (Exception $e) {
            Log::error("Xero token refresh failed: " . $e->getMessage());
            return false;
        }
    }

    /**
     * Refresh Google token
     */
    private function refreshGoogleToken(OAuthToken $token): bool
    {
        // Implement Google token refresh logic
        // This would use Google's OAuth2 client library
        return false; // Placeholder
    }

    /**
     * Refresh Microsoft token
     */
    private function refreshMicrosoftToken(OAuthToken $token): bool
    {
        // Implement Microsoft token refresh logic
        // This would use Microsoft Graph SDK
        return false; // Placeholder
    }

    /**
     * Revoke token
     */
    public function revokeToken(string $provider, array $options = []): bool
    {
        $query = OAuthToken::where('provider', $provider);

        if (isset($options['user_id'])) {
            $query->where('user_id', $options['user_id']);
        }

        if (isset($options['tenant_id'])) {
            $query->where('tenant_id', $options['tenant_id']);
        }

        $tokens = $query->get();

        foreach ($tokens as $token) {
            try {
                // Revoke at provider if possible
                $this->revokeAtProvider($token);
            } catch (Exception $e) {
                Log::warning("Failed to revoke token at provider: " . $e->getMessage());
            }

            // Delete from database
            $token->delete();
        }

        return true;
    }

    /**
     * Revoke token at provider
     */
    private function revokeAtProvider(OAuthToken $token): void
    {
        switch ($token->provider) {
            case 'xero':
                $xeroAuthService = app(\App\Modules\Xero\Services\XeroAuthService::class);
                $xeroAuthService->revokeToken();
                break;
            // Add other providers as needed
        }
    }

    /**
     * Get all tokens for a provider
     */
    public function getProviderTokens(string $provider): array
    {
        return OAuthToken::where('provider', $provider)
            ->where('status', 'active')
            ->orderBy('connected_at', 'desc')
            ->get()
            ->toArray();
    }

    /**
     * Get token statistics
     */
    public function getTokenStats(): array
    {
        return [
            'total_active' => OAuthToken::where('status', 'active')->count(),
            'total_expired' => OAuthToken::where('status', 'expired')->count(),
            'by_provider' => OAuthToken::selectRaw('provider, COUNT(*) as count')
                ->where('status', 'active')
                ->groupBy('provider')
                ->pluck('count', 'provider')
                ->toArray(),
            'expiring_soon' => OAuthToken::where('status', 'active')
                ->where('expires_at', '<=', Carbon::now()->addHours(24))
                ->whereNotNull('expires_at')
                ->count()
        ];
    }
} 