import 'dart:async';

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../core/contracts/app_update.dart';
import '../features/update/update_service.dart';
import '../shared/utils/app_logger.dart';
import 'shared/settings_persistence.dart';

enum UpdateStatus {
  idle,
  checking,
  upToDate,
  available,
  downloading,
  readyToInstall,
  error,
}

class UpdateState {
  final UpdateStatus status;
  final AppUpdateInfo? info;
  final int received;
  final int total;


  final double speedBytesPerSec;
  final String? error;
  final bool silent;
  final String? installerPath;

  const UpdateState({
    this.status = UpdateStatus.idle,
    this.info,
    this.received = 0,
    this.total = 0,
    this.speedBytesPerSec = 0,
    this.error,
    this.silent = false,
    this.installerPath,
  });

  UpdateState copyWith({
    UpdateStatus? status,
    AppUpdateInfo? info,
    int? received,
    int? total,
    double? speedBytesPerSec,
    String? error,
    bool? silent,
    String? installerPath,
    bool clearInfo = false,
    bool clearError = false,
    bool clearInstallerPath = false,
  }) {
    return UpdateState(
      status: status ?? this.status,
      info: clearInfo ? null : info ?? this.info,
      received: received ?? this.received,
      total: total ?? this.total,
      speedBytesPerSec: speedBytesPerSec ?? this.speedBytesPerSec,
      error: clearError ? null : error ?? this.error,
      silent: silent ?? this.silent,
      installerPath: clearInstallerPath
          ? null
          : installerPath ?? this.installerPath,
    );
  }
}

final updateProvider = NotifierProvider<UpdateNotifier, UpdateState>(
  UpdateNotifier.new,
);

class UpdateNotifier extends Notifier<UpdateState> with SettingsPersistence {
  static const _tag = 'Update';
  static const _lastCheckKey = 'smplayer.update.last_check';
  static const _silentThrottle = Duration(hours: 12);

  final UpdateService _service;
  CancelToken? _cancelToken;

  DateTime? _lastSpeedSampleAt;
  int _lastSpeedSampleBytes = 0;

  UpdateNotifier({UpdateService? service})
    : _service = service ?? UpdateService();

  @override
  UpdateState build() => const UpdateState();

  Future<void> silentCheck() async {
    final prefs = await getPrefs();
    final lastCheck = prefs.getInt(_lastCheckKey);
    final now = DateTime.now().millisecondsSinceEpoch;
    if (lastCheck != null && now - lastCheck < _silentThrottle.inMilliseconds) {
      AppLogger.debug(_tag, 'silent check throttled');
      return;
    }

    try {
      await prefs.setInt(_lastCheckKey, now);
      state = state.copyWith(
        status: UpdateStatus.checking,
        silent: true,
        received: 0,
        total: 0,
        clearError: true,
        clearInstallerPath: true,
      );
      final result = await _service.check();
      if (result.hasUpdate && result.info != null) {
        state = state.copyWith(
          status: UpdateStatus.available,
          info: result.info,
          silent: true,
        );
      } else {
        state = state.copyWith(
          status: UpdateStatus.idle,
          silent: true,
          clearInfo: true,
        );
      }
    } catch (e) {
      AppLogger.warn(_tag, 'silent check failed', e);
      state = state.copyWith(
        status: UpdateStatus.idle,
        silent: true,
        clearError: true,
      );
    }
  }

  Future<void> manualCheck() async {
    try {
      state = state.copyWith(
        status: UpdateStatus.checking,
        silent: false,
        received: 0,
        total: 0,
        clearError: true,
        clearInstallerPath: true,
      );
      final result = await _service.check();
      await persistField(_lastCheckKey, DateTime.now().millisecondsSinceEpoch);
      if (result.hasUpdate && result.info != null) {
        state = state.copyWith(
          status: UpdateStatus.available,
          info: result.info,
          silent: false,
        );
      } else {
        state = state.copyWith(
          status: UpdateStatus.upToDate,
          silent: false,
          clearInfo: true,
        );
      }
    } catch (e, stack) {
      AppLogger.error(_tag, 'manual check failed', e, stack);
      state = state.copyWith(
        status: UpdateStatus.error,
        error: '检查更新失败，请稍后重试',
        silent: false,
      );
    }
  }

  Future<void> startDownload() async {
    final info = state.info;
    final asset = info?.selectedAsset;
    if (info == null || asset == null) return;

    _cancelToken?.cancel('restart download');
    final cancelToken = CancelToken();
    _cancelToken = cancelToken;

    _lastSpeedSampleAt = null;
    _lastSpeedSampleBytes = 0;

    try {
      state = state.copyWith(
        status: UpdateStatus.downloading,
        received: 0,
        total: asset.size,
        speedBytesPerSec: 0,
        clearError: true,
        clearInstallerPath: true,
      );
      final file = await _service.download(
        asset,
        cancelToken: cancelToken,
        onReceiveProgress: (received, total) {
          if (cancelToken.isCancelled) return;
          state = state.copyWith(
            status: UpdateStatus.downloading,
            received: received,
            total: total > 0 ? total : asset.size,
            speedBytesPerSec: _sampleSpeed(received),
          );
        },
      );
      if (cancelToken.isCancelled) return;
      state = state.copyWith(
        status: UpdateStatus.readyToInstall,
        received: state.total,
        speedBytesPerSec: 0,
        installerPath: file.path,
        clearError: true,
      );
    } on UpdateVerificationException catch (e, stack) {
      AppLogger.error(_tag, 'update verification failed', e, stack);
      state = state.copyWith(
        status: UpdateStatus.error,
        speedBytesPerSec: 0,
        error: e.message,
      );
      return;
    } on DioException catch (e, stack) {
      if (CancelToken.isCancel(e)) {
        state = state.copyWith(
          status: UpdateStatus.available,
          received: 0,
          total: asset.size,
          speedBytesPerSec: 0,
          clearError: true,
        );
        return;
      }
      AppLogger.error(_tag, 'download failed', e, stack);
      state = state.copyWith(
        status: UpdateStatus.error,
        speedBytesPerSec: 0,
        error: '下载失败，请稍后重试',
      );
    } catch (e, stack) {
      AppLogger.error(_tag, 'download failed', e, stack);
      state = state.copyWith(
        status: UpdateStatus.error,
        speedBytesPerSec: 0,
        error: '下载失败，请稍后重试',
      );
    } finally {
      if (identical(_cancelToken, cancelToken)) {
        _cancelToken = null;
      }
    }
  }


  double? _sampleSpeed(int received) {
    final now = DateTime.now();
    final lastAt = _lastSpeedSampleAt;
    if (lastAt == null) {
      _lastSpeedSampleAt = now;
      _lastSpeedSampleBytes = received;
      return null;
    }
    final elapsedMs = now.difference(lastAt).inMilliseconds;
    if (elapsedMs < 500) return null;
    final delta = received - _lastSpeedSampleBytes;
    _lastSpeedSampleAt = now;
    _lastSpeedSampleBytes = received;
    return delta > 0 ? delta * 1000.0 / elapsedMs : 0.0;
  }

  void cancel() {
    _cancelToken?.cancel('cancelled by user');
    _cancelToken = null;
    final info = state.info;
    state = state.copyWith(
      status: info == null ? UpdateStatus.idle : UpdateStatus.available,
      received: 0,
      total: info?.selectedAsset?.size ?? 0,
      speedBytesPerSec: 0,
      clearError: true,
    );
  }

  Future<void> install() async {
    final path = state.installerPath;
    if (path == null || path.isEmpty) return;

    try {
      await _service.launchInstaller(path);
    } catch (e, stack) {
      AppLogger.error(_tag, 'launch installer failed', e, stack);
      state = state.copyWith(status: UpdateStatus.error, error: '安装程序启动失败');
    }
  }
}
