import 'dart:async';

import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/calendar/v3.dart' as gcal;
import 'package:http/http.dart' as http;
import 'package:http/retry.dart';

import 'app_settings_repository.dart';

/// Googleカレンダーとの認証・イベント同期を担当するサービス。
class CalendarSyncService {
  CalendarSyncService({AppSettingsRepository? settingsRepository})
      : _settingsRepository = settingsRepository ?? AppSettingsRepository();

  final AppSettingsRepository _settingsRepository;
  GoogleSignInAccount? _currentAccount;
  gcal.CalendarApi? _calendarApi;
  static const List<String> _scopes = [gcal.CalendarApi.calendarScope];

  GoogleSignIn get _googleSignIn => GoogleSignIn(scopes: _scopes);

  Future<bool> ensureSignedIn() async {
    final enabled = await _settingsRepository.getGoogleCalendarEnabled();
    if (!enabled) return false;
    _currentAccount = await _googleSignIn.signInSilently();
    _currentAccount ??= await _googleSignIn.signIn();
    if (_currentAccount == null) {
      await _settingsRepository.clearGoogleCalendarSettings();
      return false;
    }
    await _initializeCalendarApi();
    await _settingsRepository.setGoogleCalendarAccountEmail(_currentAccount!.email);
    return true;
  }

  Future<void> signOut() async {
    await _googleSignIn.disconnect();
    await _settingsRepository.clearGoogleCalendarSettings();
    _calendarApi = null;
  }

  Future<void> _initializeCalendarApi() async {
    final account = _currentAccount;
    if (account == null) return;
    final authHeaders = await account.authHeaders;
    final authClient = _AuthClientDecorator(authHeaders, http.Client());
    final retryClient = RetryClient(authClient, retries: 3);
    _calendarApi = gcal.CalendarApi(retryClient);
  }

  Future<List<gcal.CalendarListEntry>> fetchCalendars() async {
    if (_calendarApi == null) {
      final ready = await ensureSignedIn();
      if (!ready) return [];
    }
    final list = await _calendarApi!.calendarList.list(showHidden: false, minAccessRole: 'writer');
    return list.items ?? [];
  }

  Future<void> createOrUpdateEvent({
    required String eventId,
    required String summary,
    String? description,
    DateTime? start,
    DateTime? end,
    String? calendarId,
    Map<String, String>? extendedProperties,
  }) async {
    if (_calendarApi == null) {
      final ready = await ensureSignedIn();
      if (!ready) return;
    }
    final targetCalendarId = calendarId ?? (await _settingsRepository.getGoogleCalendarId()) ?? 'primary';
    final event = gcal.Event()
      ..id = eventId
      ..summary = summary
      ..description = description
      ..start = start != null ? _timeFromDate(start) : null
      ..end = end != null ? _timeFromDate(end) : null
      ..extendedProperties = extendedProperties == null
          ? null
          : gcal.EventExtendedProperties(private: extendedProperties);

    try {
      await _calendarApi!.events.patch(event, targetCalendarId, eventId);
    } on gcal.DetailedApiRequestError catch (e) {
      if (e.status == 404) {
        await _calendarApi!.events.insert(event, targetCalendarId);
      } else {
        rethrow;
      }
    }
  }

  Future<void> deleteEvent(String eventId, {String? calendarId}) async {
    if (_calendarApi == null) {
      final ready = await ensureSignedIn();
      if (!ready) return;
    }
    final targetCalendarId = calendarId ?? (await _settingsRepository.getGoogleCalendarId()) ?? 'primary';
    await _calendarApi!.events.delete(targetCalendarId, eventId);
  }

  gcal.EventDateTime _timeFromDate(DateTime date) {
    return gcal.EventDateTime(dateTime: date, timeZone: date.timeZoneName);
  }
}

class _AuthClientDecorator extends http.BaseClient {
  _AuthClientDecorator(this._headers, this._inner);

  final Map<String, String> _headers;
  final http.Client _inner;

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) {
    request.headers.addAll(_headers);
    return _inner.send(request);
  }

  @override
  void close() {
    _inner.close();
    super.close();
  }
}
