<?php

namespace App\Services;

use App\Events\CalendarUpdated;
use App\Models\CalendarIntegration;
use App\Models\Event;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class GoogleCalendarService
{
    private const TOKEN_URL    = 'https://oauth2.googleapis.com/token';
    private const CALENDAR_API = 'https://www.googleapis.com/calendar/v3/calendars';
    private const CHANNELS_STOP = 'https://www.googleapis.com/calendar/v3/channels/stop';

    /**
     * Verhindert Sync-Schleifen: true wenn wir gerade Google-Daten importieren.
     */
    public static bool $syncing = false;

    // ── Token-Management ──────────────────────────────────────────────────

    public function getValidToken(CalendarIntegration $integration): ?string
    {
        if ($integration->isExpired() && $integration->refresh_token) {
            $this->refreshToken($integration);
            $integration->refresh();
        }

        return $integration->rawAccessToken();
    }

    private function refreshToken(CalendarIntegration $integration): void
    {
        $response = Http::asForm()->post(self::TOKEN_URL, [
            'client_id'     => config('services.google_calendar.client_id'),
            'client_secret' => config('services.google_calendar.client_secret'),
            'refresh_token' => $integration->refresh_token,
            'grant_type'    => 'refresh_token',
        ]);

        if ($response->failed()) {
            Log::error('Google Calendar: Token-Refresh fehlgeschlagen', [
                'user_id' => $integration->user_id,
                'status'  => $response->status(),
            ]);
            return;
        }

        $data = $response->json();

        $integration->update([
            'access_token'     => $data['access_token'],
            'token_expires_at' => now()->addSeconds($data['expires_in'] ?? 3600),
        ]);
    }

    // ── Push: Lokal → Google ──────────────────────────────────────────────

    /**
     * Erstellt oder aktualisiert ein Event in Google Calendar.
     * Setzt google_event_id auf dem lokalen Event.
     */
    public function pushEvent(Event $event, CalendarIntegration $integration): void
    {
        $token = $this->getValidToken($integration);
        if (!$token) return;

        $userTz = User::find($integration->user_id)?->timezone ?? config('app.timezone', 'UTC');
        $body = $this->toGoogleEvent($event, $userTz);
        $calendarId = urlencode($integration->calendar_id ?? 'primary');

        Log::info('Google Calendar: Push-Debug', [
            'event_id'    => $event->id,
            'db_start'    => $event->starts_at?->toIso8601String(),
            'user_tz'     => $userTz,
            'google_start' => $body['start']['dateTime'] ?? $body['start']['date'] ?? null,
            'google_tz'    => $body['start']['timeZone'] ?? null,
        ]);

        if ($event->google_event_id) {
            // Update
            $response = Http::withToken($token)
                ->put(self::CALENDAR_API."/{$calendarId}/events/{$event->google_event_id}", $body);
        } else {
            // Create
            $response = Http::withToken($token)
                ->post(self::CALENDAR_API."/{$calendarId}/events", $body);
        }

        if ($response->failed()) {
            Log::error('Google Calendar: Push fehlgeschlagen', [
                'user_id'  => $integration->user_id,
                'event_id' => $event->id,
                'status'   => $response->status(),
                'body'     => $response->body(),
            ]);
            return;
        }

        $googleId = $response->json('id');

        if ($googleId && $googleId !== $event->google_event_id) {
            // Ohne Observer-Trigger direkt in DB schreiben (forceFill umgeht Mass-Assignment)
            Event::withoutEvents(function () use ($event, $googleId) {
                $event->forceFill(['google_event_id' => $googleId])->save();
            });
        }
    }

    /**
     * Löscht ein Event in Google Calendar.
     */
    public function deleteEvent(string $googleEventId, CalendarIntegration $integration): void
    {
        $token = $this->getValidToken($integration);
        if (!$token) return;

        $calendarId = urlencode($integration->calendar_id ?? 'primary');

        $response = Http::withToken($token)
            ->delete(self::CALENDAR_API."/{$calendarId}/events/{$googleEventId}");

        if ($response->failed() && $response->status() !== 410) {
            Log::error('Google Calendar: Delete fehlgeschlagen', [
                'user_id'         => $integration->user_id,
                'google_event_id' => $googleEventId,
                'status'          => $response->status(),
            ]);
        }
    }

    // ── Pull: Google → Lokal ──────────────────────────────────────────────

    /**
     * Importiert alle neuen/geänderten Events von Google Calendar.
     * Nutzt nextSyncToken für inkrementellen Sync; fällt auf Vollsync zurück bei 410.
     */
    public function pullEvents(CalendarIntegration $integration): void
    {
        $token = $this->getValidToken($integration);
        if (!$token) {
            Log::warning('Google Calendar: Kein gültiger Token für Pull-Sync', [
                'user_id'    => $integration->user_id,
                'is_expired' => $integration->isExpired(),
            ]);
            return;
        }

        $user = User::find($integration->user_id);
        if (!$user) return;

        $calendarId = urlencode($integration->calendar_id ?? 'primary');
        $url        = self::CALENDAR_API."/{$calendarId}/events";

        // WICHTIG: orderBy/singleEvents dürfen nicht zusammen mit syncToken verwendet werden
        // und orderBy verhindert die Rückgabe von nextSyncToken auch beim Vollsync.
        if ($integration->rawSyncToken()) {
            // Inkrementeller Sync: NUR syncToken, keine weiteren Filter (Google-Vorschrift)
            $params = [
                'maxResults'  => 250,
                'syncToken'   => $integration->rawSyncToken(),
                'showDeleted' => 'true',
            ];
        } else {
            // Erster Vollsync: singleEvents ohne orderBy → Google liefert nextSyncToken
            $params = [
                'maxResults'   => 250,
                'singleEvents' => 'true',
                'timeMin'      => now()->subDays(30)->toRfc3339String(),
            ];
        }

        self::$syncing = true;

        try {
            $imported = $this->fetchAndImportPages($url, $params, $token, $integration, $user);

            if ($imported > 0) {
                broadcast(new CalendarUpdated($user->id));
            }
        } catch (\Exception $e) {
            Log::error('Google Calendar: Pull fehlgeschlagen', [
                'user_id' => $integration->user_id,
                'error'   => $e->getMessage(),
            ]);
        } finally {
            self::$syncing = false;
        }
    }

    private function fetchAndImportPages(
        string $url,
        array $params,
        string $token,
        CalendarIntegration $integration,
        User $user
    ): int {
        $pageToken    = null;
        $importCount  = 0;

        do {
            $query = $params;
            if ($pageToken) {
                $query['pageToken'] = $pageToken;
            }

            $response = Http::withToken($token)->get($url, $query);

            // 410 Gone = syncToken ungültig → Vollsync
            if ($response->status() === 410) {
                Log::info('Google Calendar: syncToken abgelaufen, starte Vollsync', [
                    'user_id' => $integration->user_id,
                ]);
                $integration->forceFill(['sync_token' => null])->save();
                unset($params['syncToken']);
                $params['timeMin'] = now()->subDays(30)->toRfc3339String();
                $response = Http::withToken($token)->get($url, $params);
            }

            if ($response->failed()) {
                Log::error('Google Calendar: API-Fehler beim Pull', [
                    'user_id' => $integration->user_id,
                    'status'  => $response->status(),
                ]);
                break;
            }

            $data = $response->json();
            $items = $data['items'] ?? [];

            Log::info('Google Calendar: Pull-Antwort', [
                'user_id'    => $integration->user_id,
                'status'     => $response->status(),
                'items'      => count($items),
                'has_sync'   => isset($params['syncToken']),
                'has_next'   => !empty($data['nextSyncToken']),
            ]);

            foreach ($items as $item) {
                try {
                    $this->importGoogleEvent($item, $user);
                    $importCount++;
                    Log::info('Google Calendar: Event importiert', [
                        'user_id'         => $user->id,
                        'google_event_id' => $item['id'] ?? null,
                        'summary'         => $item['summary'] ?? null,
                        'status'          => $item['status'] ?? null,
                    ]);
                } catch (\Throwable $e) {
                    Log::error('Google Calendar: Einzelnes Event konnte nicht importiert werden', [
                        'user_id'  => $user->id,
                        'event_id' => $item['id'] ?? null,
                        'summary'  => $item['summary'] ?? null,
                        'error'    => $e->getMessage(),
                    ]);
                }
            }

            $pageToken = $data['nextPageToken'] ?? null;

            // nextSyncToken nur auf der letzten Seite gesetzt
            if (!empty($data['nextSyncToken'])) {
                $integration->update([
                    'sync_token'    => $data['nextSyncToken'],
                    'last_synced_at' => now(),
                ]);
            }

        } while ($pageToken);

        return $importCount;
    }

    private function importGoogleEvent(array $item, User $user): void
    {
        $googleId = $item['id'] ?? null;
        if (!$googleId) return;

        // Abgesagte Events lokal löschen
        if (($item['status'] ?? '') === 'cancelled') {
            Event::withoutEvents(
                fn () => Event::where('user_id', $user->id)
                    ->where('google_event_id', $googleId)
                    ->delete()
            );
            return;
        }

        $mapped = $this->fromGoogleEvent($item);
        if (!$mapped) return; // Event ohne Zeitangabe überspringen

        $existing = Event::where('user_id', $user->id)
            ->where('google_event_id', $googleId)
            ->first();

        Event::withoutEvents(function () use ($existing, $mapped, $user, $googleId) {
            if ($existing) {
                $existing->forceFill($mapped)->save();
            } else {
                (new Event)->forceFill(array_merge($mapped, [
                    'id'              => (string) \Illuminate\Support\Str::uuid(),
                    'user_id'         => $user->id,
                    'google_event_id' => $googleId,
                ]))->save();
            }
        });
    }

    // ── Watch: Google Push-Benachrichtigungen ─────────────────────────────

    /**
     * Registriert einen Google Watch-Kanal.
     * Google sendet dann einen POST-Webhook bei jeder Kalenderänderung.
     * Watch läuft max. 7 Tage — muss vor Ablauf erneuert werden.
     */
    public function createWatch(CalendarIntegration $integration): void
    {
        $token = $this->getValidToken($integration);
        if (!$token) return;

        // Alten Watch stoppen falls vorhanden
        $this->stopWatch($integration);

        $channelId  = (string) \Illuminate\Support\Str::uuid();
        $calendarId = urlencode($integration->calendar_id ?? 'primary');
        $webhookUrl = config('services.google_calendar.webhook_url') ?? route('webhooks.google-calendar');

        $response = Http::withToken($token)
            ->post(self::CALENDAR_API."/{$calendarId}/events/watch", [
                'id'      => $channelId,
                'type'    => 'web_hook',
                'address' => $webhookUrl,
            ]);

        if ($response->failed()) {
            Log::error('Google Calendar: Watch-Registrierung fehlgeschlagen', [
                'user_id' => $integration->user_id,
                'status'  => $response->status(),
                'body'    => $response->body(),
            ]);
            return;
        }

        $data = $response->json();

        // Ablaufzeit kommt in Millisekunden
        $expiresAt = isset($data['expiration'])
            ? \Carbon\Carbon::createFromTimestampMs($data['expiration'])
            : now()->addDays(7);

        $integration->update([
            'watch_channel_id'  => $data['id']         ?? $channelId,
            'watch_resource_id' => $data['resourceId'] ?? null,
            'watch_expires_at'  => $expiresAt,
        ]);

        Log::info('Google Calendar: Watch registriert', [
            'user_id'    => $integration->user_id,
            'channel_id' => $data['id'] ?? $channelId,
            'expires_at' => $expiresAt->toDateTimeString(),
        ]);
    }

    /**
     * Stoppt einen aktiven Watch-Kanal bei Google.
     */
    public function stopWatch(CalendarIntegration $integration): void
    {
        if (!$integration->watch_channel_id || !$integration->watch_resource_id) {
            return;
        }

        $token = $this->getValidToken($integration);
        if (!$token) return;

        Http::withToken($token)->post(self::CHANNELS_STOP, [
            'id'         => $integration->watch_channel_id,
            'resourceId' => $integration->watch_resource_id,
        ]);

        $integration->update([
            'watch_channel_id'  => null,
            'watch_resource_id' => null,
            'watch_expires_at'  => null,
        ]);
    }

    // ── Feld-Mapping ──────────────────────────────────────────────────────

    /**
     * Konvertiert ein lokales Event in das Google Calendar API-Format.
     */
    private function toGoogleEvent(Event $event, string $timezone): array
    {
        if ($event->is_all_day) {
            return [
                'summary'     => $event->title,
                'description' => $event->notes,
                'start'       => ['date' => $event->starts_at->toDateString()],
                'end'         => ['date' => ($event->ends_at ?? $event->starts_at)->toDateString()],
            ];
        }

        $start = $event->starts_at->copy()->setTimezone($timezone);
        $end   = ($event->ends_at ?? $event->starts_at->copy()->addHour())->copy()->setTimezone($timezone);

        return [
            'summary'     => $event->title,
            'description' => $event->notes,
            'start'       => [
                'dateTime' => $start->toRfc3339String(),
                'timeZone' => $timezone,
            ],
            'end'         => [
                'dateTime' => $end->toRfc3339String(),
                'timeZone' => $timezone,
            ],
        ];
    }

    /**
     * Konvertiert ein Google Calendar Event in lokale Felder.
     * Gibt null zurück wenn keine gültige Zeitangabe vorhanden.
     */
    private function fromGoogleEvent(array $item): ?array
    {
        $isAllDay  = isset($item['start']['date']);
        $startsRaw = $item['start']['dateTime'] ?? $item['start']['date'] ?? null;
        $endsRaw   = $item['end']['dateTime']   ?? $item['end']['date']   ?? null;

        if (!$startsRaw) return null;

        $startTz  = $item['start']['timeZone'] ?? 'UTC';
        $endTz    = $item['end']['timeZone']   ?? $startTz;
        $startsAt = Carbon::parse($startsRaw, $startTz)->utc();
        $endsAt   = $endsRaw ? Carbon::parse($endsRaw, $endTz)->utc() : null;

        Log::info('Google Calendar: Pull-Debug', [
            'google_event_id' => $item['id'] ?? null,
            'raw_start'       => $startsRaw,
            'raw_tz'          => $startTz,
            'parsed_utc'      => $startsAt->toIso8601String(),
        ]);

        return [
            'title'      => $item['summary'] ?? '(Kein Titel)',
            'notes'      => $item['description'] ?? null,
            'is_all_day' => $isAllDay,
            'starts_at'  => $startsAt,
            'ends_at'    => $endsAt,
            'start_date' => $isAllDay ? $startsAt->toDateString() : null,
            'end_date'   => ($isAllDay && $endsAt) ? $endsAt->toDateString() : null,
        ];
    }
}
