import 'dart:async';

import '../../../core/contracts/trakt/trakt_response.dart';
import '../../../core/contracts/trakt/trakt_scrobble_target.dart';
import '../../../core/contracts/trakt/trakt_sync_job.dart';
import '../../../core/domain/ports/authed_trakt_gateway.dart';
import '../../../core/infra/trakt_api/trakt_pending_sync_queue.dart';
import '../../../shared/utils/app_logger.dart';
import 'trakt_scrobble_state_machine.dart';

const _tag = 'TraktScrobble';


class TraktScrobbleReporter {
  TraktScrobbleReporter({
    required this._gateway,
    required this._queue,
    required this._target,
    required this._readProgress,
    this._appVersion,
    this._appDate,
    this._onSuccess,
    this._onError,
  });

  final AuthedTraktGateway _gateway;
  final TraktPendingSyncQueue _queue;
  final TraktScrobbleTarget? _target;
  final double Function() _readProgress;
  final String? _appVersion;
  final String? _appDate;
  final void Function()? _onSuccess;
  final void Function(String error)? _onError;

  final _sm = TraktScrobbleStateMachine();

  bool get _enabled => _target != null;

  Future<void> onPlay() => _dispatch(_sm.onPlay(), _readProgress());

  Future<void> onPause() => _dispatch(_sm.onPause(), _readProgress());

  Future<void> onCompleted() => _dispatch(_sm.onCompleted(), 100);


  Future<void> onStop() =>
      _dispatch(_sm.onStop(progress: _readProgress()), _readProgress());

  Future<void> _dispatch(TraktScrobbleAction action, double progress) async {
    if (!_enabled) return;
    final type = _typeFor(action);
    if (type == null) return;

    final clamped = progress.clamp(0.0, 100.0).toDouble();
    final body = _target!.toBody(
      progress: clamped,
      appVersion: _appVersion,
      appDate: _appDate,
    );

    AppLogger.debug(
      _tag,
      'scrobble ${type.wire} progress=${clamped.toStringAsFixed(1)} '
      '(${_target.kind.name})',
    );

    TraktScrobbleOutcome outcome;
    try {
      outcome = await _gateway.scrobble(type, body);
    } catch (e) {
      AppLogger.debug(_tag, 'scrobble threw, enqueue', e);
      await _enqueue(type, body);
      return;
    }

    switch (outcome.cls) {
      case TraktResponseClass.success:
      case TraktResponseClass.duplicate:
        AppLogger.debug(_tag, 'scrobble ${type.wire} ok (${outcome.cls.name})');
        await _queue.discardSupersededBy(type: type, body: body);
        _onSuccess?.call();
      case TraktResponseClass.invalid:
        AppLogger.debug(_tag, 'scrobble ${type.wire} invalid → drop');
      case TraktResponseClass.rateLimited:
      case TraktResponseClass.unauthorized:
      case TraktResponseClass.transient:
        await _enqueue(type, body);
        _onError?.call(outcome.cls.name);
    }
  }

  Future<void> _enqueue(
    TraktSyncJobType type,
    Map<String, dynamic> body,
  ) async {
    try {
      await _queue.enqueue(type: type, body: body);
    } catch (e) {
      AppLogger.warn(_tag, 'enqueue failed', e);
    }
  }

  static TraktSyncJobType? _typeFor(TraktScrobbleAction action) {
    switch (action) {
      case TraktScrobbleAction.none:
        return null;
      case TraktScrobbleAction.start:
        return TraktSyncJobType.scrobbleStart;
      case TraktScrobbleAction.pause:
        return TraktSyncJobType.scrobblePause;
      case TraktScrobbleAction.stop:
        return TraktSyncJobType.scrobbleStop;
    }
  }
}
