import 'package:json_annotation/json_annotation.dart';

import '../trackers/tracker_session_utils.dart';

part 'trakt_session.g.dart';

/// Immutable Trakt OAuth session.
///
/// Persisted as a JSON blob under `user_{uuid}_trakt_session` in
/// `SharedPreferences`. Tokens are stored in plaintext, matching the security
/// model of the existing Plex token.
@JsonSerializable(fieldRename: FieldRename.snake)
class TraktSession {
  final String accessToken;
  final String refreshToken;

  /// Epoch seconds at which the access token expires.
  final int expiresAt;

  /// Trakt username (`@handle`), populated after `getUserSettings`.
  final String? username;

  @JsonKey(defaultValue: 'public')
  final String scope;

  /// Epoch seconds at which the session was first created.
  final int createdAt;

  const TraktSession({
    required this.accessToken,
    required this.refreshToken,
    required this.expiresAt,
    required this.scope,
    required this.createdAt,
    this.username,
  });

  /// Whether the access token has already expired.
  bool get isExpired => isTrackerTokenExpired(expiresAt);

  /// Whether the access token will expire in the next 5 minutes.
  bool get needsRefresh => trackerTokenNeedsRefresh(expiresAt);

  TraktSession copyWith({
    String? accessToken,
    String? refreshToken,
    int? expiresAt,
    String? username,
    String? scope,
    int? createdAt,
  }) {
    return TraktSession(
      accessToken: accessToken ?? this.accessToken,
      refreshToken: refreshToken ?? this.refreshToken,
      expiresAt: expiresAt ?? this.expiresAt,
      username: username ?? this.username,
      scope: scope ?? this.scope,
      createdAt: createdAt ?? this.createdAt,
    );
  }

  Map<String, dynamic> toJson() => _$TraktSessionToJson(this);

  factory TraktSession.fromJson(Map<String, dynamic> json) => _$TraktSessionFromJson(json);

  /// Build a session from Trakt's `/oauth/token` or `/oauth/device/token` response,
  /// which uses `expires_in` (relative seconds) rather than `expires_at`.
  factory TraktSession.fromTokenResponse(Map<String, dynamic> json) {
    final createdAt = (json['created_at'] as num?)?.toInt() ?? trackerSessionNowEpochSeconds();
    final expiresIn = (json['expires_in'] as num).toInt();
    return TraktSession(
      accessToken: json['access_token'] as String,
      refreshToken: json['refresh_token'] as String,
      expiresAt: createdAt + expiresIn,
      scope: json['scope'] as String? ?? 'public',
      createdAt: createdAt,
    );
  }

  String encode() => encodeTrackerSessionJson(toJson());
  static TraktSession decode(String raw) => decodeTrackerSessionJson(raw, TraktSession.fromJson);
}
