import 'dart:async';

import '../../../shared/utils/app_logger.dart';
import '../../contracts/trakt/trakt_token.dart';
import '../../domain/ports/trakt_gateway.dart';
import 'trakt_token_store.dart';

const _tag = 'TraktAuth';


class TraktAuth {
  TraktAuth({
    required this.store,
    required this.gateway,
    this.refreshBufferSeconds = 24 * 3600,
  });

  final TraktTokenStore store;
  final TraktGateway gateway;


  final int refreshBufferSeconds;


  void Function()? onCleared;

  Future<TraktToken>? _inflightRefresh;
  int _generation = 0;

  Future<bool> isConnected() async => (await store.read()) != null;


  Future<String?> validAccessToken() async {
    final token = await store.read();
    if (token == null || token.accessToken.isEmpty) return null;

    final nowSec = DateTime.now().millisecondsSinceEpoch ~/ 1000;
    final expiresAt = token.createdAt + token.expiresIn;
    final notExpiring =
        token.expiresIn <= 0 || nowSec < expiresAt - refreshBufferSeconds;
    if (notExpiring) return token.accessToken;

    if (token.refreshToken.isEmpty) {
      await _clear();
      return null;
    }

    try {
      final fresh = await _refreshSingleFlight(token.refreshToken);
      return fresh.accessToken;
    } on _TraktRefreshCancelled {
      return null;
    } catch (e) {
      AppLogger.warn(_tag, 'refresh failed, clearing token', e);
      await _clear();
      return null;
    }
  }


  Future<String?> forceRefresh() async {
    final token = await store.read();
    if (token == null || token.refreshToken.isEmpty) {
      await _clear();
      return null;
    }
    try {
      final fresh = await _refreshSingleFlight(token.refreshToken);
      return fresh.accessToken;
    } on _TraktRefreshCancelled {
      return null;
    } catch (e) {
      AppLogger.warn(_tag, 'force refresh failed, clearing token', e);
      await _clear();
      return null;
    }
  }


  Future<void> persist(TraktToken token) => store.write(token);


  Future<void> clear() => _clear();

  Future<TraktToken> _refreshSingleFlight(String refreshToken) {
    final existing = _inflightRefresh;
    if (existing != null) return existing;
    final generation = _generation;
    final future = () async {
      try {
        final fresh = await gateway.refreshToken(refreshToken);
        if (generation != _generation) throw const _TraktRefreshCancelled();
        await store.write(fresh);
        AppLogger.debug(_tag, 'token refreshed');
        return fresh;
      } finally {
        _inflightRefresh = null;
      }
    }();
    _inflightRefresh = future;
    return future;
  }

  Future<void> _clear() async {
    _generation++;
    await store.clear();
    onCleared?.call();
  }
}

class _TraktRefreshCancelled implements Exception {
  const _TraktRefreshCancelled();
}
