<?php

namespace App\Services;

use Google_Client;
use Google_Service_Calendar;
use Google_Service_Calendar_Event;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class OptimizedGoogleCalendarService
{
    protected ?Google_Client $client = null;
    protected ?Google_Service_Calendar $service = null;
    protected const CACHE_TTL = 3600; // 1 hour
    protected const CIRCUIT_BREAKER_KEY = 'google_calendar:circuit_breaker';
    protected const CIRCUIT_BREAKER_THRESHOLD = 5; // failures
    protected const CIRCUIT_BREAKER_TIMEOUT = 300; // 5 minutes

    public function __construct()
    {
        $this->initializeClient();
    }

    /**
     * Check if circuit breaker is open (too many failures)
     */
    protected function isCircuitOpen(): bool
    {
        $failures = Cache::get(self::CIRCUIT_BREAKER_KEY . ':failures', 0);
        $lastFailure = Cache::get(self::CIRCUIT_BREAKER_KEY . ':last_failure');

        if ($failures >= self::CIRCUIT_BREAKER_THRESHOLD && $lastFailure) {
            $timeSinceLastFailure = now()->diffInSeconds($lastFailure);
            if ($timeSinceLastFailure < self::CIRCUIT_BREAKER_TIMEOUT) {
                Log::warning('[GoogleCalendar] Circuit breaker is OPEN');
                return true;
            }
            // Reset after timeout
            Cache::forget(self::CIRCUIT_BREAKER_KEY . ':failures');
        }

        return false;
    }

    /**
     * Record a failure for circuit breaker
     */
    protected function recordFailure(): void
    {
        $failures = Cache::increment(self::CIRCUIT_BREAKER_KEY . ':failures', 1);
        Cache::put(self::CIRCUIT_BREAKER_KEY . ':last_failure', now(), self::CIRCUIT_BREAKER_TIMEOUT);

        Log::warning("[GoogleCalendar] Failure recorded. Count: {$failures}");
    }

    /**
     * Record a success (reset circuit breaker)
     */
    protected function recordSuccess(): void
    {
        Cache::forget(self::CIRCUIT_BREAKER_KEY . ':failures');
        Cache::forget(self::CIRCUIT_BREAKER_KEY . ':last_failure');
    }

    /**
     * Initialize Google Client with caching
     */
    protected function initializeClient(): void
    {
        if ($this->isCircuitOpen()) {
            throw new \Exception('Google Calendar service temporarily unavailable (circuit breaker open)');
        }

        // Try to get cached token
        $cacheKey = 'google_calendar:token';
        $cachedToken = Cache::get($cacheKey);

        if ($cachedToken) {
            $this->client = new Google_Client();
            $this->client->setAccessToken($cachedToken);

            if (!$this->client->isAccessTokenExpired()) {
                $this->service = new Google_Service_Calendar($this->client);
                return;
            }
        }

        // Fetch from DB if not cached or expired
        $token = DB::table('google_calendar_tokens')->first();

        if (!$token) {
            throw new \Exception('Google Calendar not authenticated');
        }

        // Read credentials
        $credentialsPath = storage_path('app/google/credentials.json');

        if (!file_exists($credentialsPath)) {
            throw new \Exception('Google credentials.json not found');
        }

        $credentials = json_decode(file_get_contents($credentialsPath), true);
        $clientConfig = $credentials['web'] ?? $credentials['installed'] ?? null;

        $this->client = new Google_Client();
        $this->client->setClientId($clientConfig['client_id']);
        $this->client->setClientSecret($clientConfig['client_secret']);
        $this->client->addScope(Google_Service_Calendar::CALENDAR);
        $this->client->setAccessType('offline');

        // Set token
        $tokenData = [
            'access_token' => $token->access_token,
            'refresh_token' => $token->refresh_token,
            'expires_in' => $token->expires_in,
            'created' => strtotime($token->updated_at ?? $token->created_at),
        ];

        $this->client->setAccessToken($tokenData);

        // Refresh if expired
        if ($this->client->isAccessTokenExpired()) {
            if ($token->refresh_token) {
                try {
                    $this->client->fetchAccessTokenWithRefreshToken($token->refresh_token);
                    $newToken = $this->client->getAccessToken();

                    // Update DB
                    DB::table('google_calendar_tokens')->update([
                        'access_token' => $newToken['access_token'],
                        'expires_in' => $newToken['expires_in'] ?? 3600,
                        'updated_at' => now(),
                    ]);

                    $this->recordSuccess();
                } catch (\Exception $e) {
                    $this->recordFailure();
                    throw $e;
                }
            } else {
                throw new \Exception('Token expired and no refresh token available');
            }
        }

        // Cache the valid token
        Cache::put($cacheKey, $this->client->getAccessToken(), self::CACHE_TTL);

        $this->service = new Google_Service_Calendar($this->client);
    }

    /**
     * Create event with retry logic
     */
    public function createEvent(
        string $summary,
        string $description,
        string $startDateTime,
        string $endDateTime,
        array $attendees = [],
        string $timeZone = 'Asia/Kolkata',
        int $retries = 3
    ): array {
        return $this->executeWithRetry(function () use ($summary, $description, $startDateTime, $endDateTime, $attendees, $timeZone) {
            $formattedAttendees = array_filter(array_map(
                fn($email) => $email ? ['email' => $email] : null,
                $attendees
            ));

            $event = new Google_Service_Calendar_Event([
                'summary' => $summary,
                'description' => $description,
                'start' => ['dateTime' => $startDateTime, 'timeZone' => $timeZone],
                'end' => ['dateTime' => $endDateTime, 'timeZone' => $timeZone],
                'attendees' => $formattedAttendees,
                'guestsCanInviteOthers' => true,
                'guestsCanSeeOtherGuests' => true,
                'conferenceData' => [
                    'createRequest' => [
                        'requestId' => uniqid(),
                        'conferenceSolutionKey' => ['type' => 'hangoutsMeet'],
                    ],
                ],
            ]);

            $createdEvent = $this->service->events->insert('primary', $event, [
                'conferenceDataVersion' => 1,
                'sendUpdates' => 'all',
            ]);

            $meetingLink = '';
            if (!empty($createdEvent->conferenceData->entryPoints)) {
                foreach ($createdEvent->conferenceData->entryPoints as $ep) {
                    if ($ep->entryPointType === 'video') {
                        $meetingLink = $ep->uri;
                        break;
                    }
                }
            }

            return [
                'event_id' => $createdEvent->id,
                'meeting_link' => $meetingLink,
                'html_link' => $createdEvent->htmlLink,
            ];
        }, $retries);
    }

    /**
     * Update event with retry logic
     */
    public function updateEvent(string $eventId, array $data, int $retries = 3): array
    {
        return $this->executeWithRetry(function () use ($eventId, $data) {
            $event = $this->service->events->get('primary', $eventId);

            if (isset($data['summary'])) {
                $event->setSummary($data['summary']);
            }
            if (isset($data['description'])) {
                $event->setDescription($data['description']);
            }
            if (isset($data['start'])) {
                $start = new \Google_Service_Calendar_EventDateTime();
                $start->setDateTime($data['start']);
                $start->setTimeZone($data['timeZone'] ?? 'Asia/Kolkata');
                $event->setStart($start);
            }
            if (isset($data['end'])) {
                $end = new \Google_Service_Calendar_EventDateTime();
                $end->setDateTime($data['end']);
                $end->setTimeZone($data['timeZone'] ?? 'Asia/Kolkata');
                $event->setEnd($end);
            }
            if (isset($data['attendees'])) {
                $event->setAttendees($data['attendees']);
            }

            $updated = $this->service->events->update('primary', $eventId, $event, [
                'sendUpdates' => 'all',
            ]);

            return [
                'event_id' => $updated->id,
                'html_link' => $updated->htmlLink,
            ];
        }, $retries);
    }

    /**
     * Delete event with retry logic
     */
    public function deleteEvent(string $eventId, int $retries = 3): void
    {
        $this->executeWithRetry(function () use ($eventId) {
            $this->service->events->delete('primary', $eventId);
        }, $retries);
    }

    /**
     * Execute function with exponential backoff retry
     */
    protected function executeWithRetry(callable $callback, int $retries): mixed
    {
        $lastException = null;

        for ($attempt = 1; $attempt <= $retries; $attempt++) {
            try {
                $result = $callback();
                $this->recordSuccess();
                return $result;
            } catch (\Google_Service_Exception $e) {
                $lastException = $e;
                $code = $e->getCode();

                // Don't retry on client errors (4xx)
                if ($code >= 400 && $code < 500) {
                    throw $e;
                }

                Log::warning("[GoogleCalendar] Attempt {$attempt} failed: " . $e->getMessage());

                if ($attempt < $retries) {
                    $delay = pow(2, $attempt) * 1000; // Exponential backoff in ms
                    usleep($delay * 1000);
                }
            } catch (\Exception $e) {
                $lastException = $e;
                Log::warning("[GoogleCalendar] Attempt {$attempt} failed: " . $e->getMessage());

                if ($attempt < $retries) {
                    $delay = pow(2, $attempt) * 1000;
                    usleep($delay * 1000);
                }
            }
        }

        $this->recordFailure();
        throw $lastException ?? new \Exception('All retry attempts failed');
    }
}
