import 'dart:async';
import 'dart:math';

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

import '../core/contracts/trakt/trakt_token.dart';
import '../core/infra/deep_link/deep_link_service.dart';
import '../core/infra/trakt_api/trakt_credentials.dart';
import '../core/infra/trakt_api/trakt_token_store.dart';
import '../shared/utils/app_logger.dart';
import 'di_providers.dart';
import 'shared/settings_persistence.dart';

const _tag = 'Trakt';


const _connectTimeout = Duration(minutes: 5);


class TraktState {
  final bool isConnected;
  final String username;
  final String accessToken;
  final String refreshToken;
  final int expiresIn;
  final int createdAt;

  const TraktState({
    this.isConnected = false,
    this.username = '',
    this.accessToken = '',
    this.refreshToken = '',
    this.expiresIn = 0,
    this.createdAt = 0,
  });

  TraktState copyWith({
    bool? isConnected,
    String? username,
    String? accessToken,
    String? refreshToken,
    int? expiresIn,
    int? createdAt,
  }) => TraktState(
    isConnected: isConnected ?? this.isConnected,
    username: username ?? this.username,
    accessToken: accessToken ?? this.accessToken,
    refreshToken: refreshToken ?? this.refreshToken,
    expiresIn: expiresIn ?? this.expiresIn,
    createdAt: createdAt ?? this.createdAt,
  );
}


class TraktConnectException implements Exception {
  final String message;
  const TraktConnectException(this.message);

  @override
  String toString() => 'TraktConnectException: $message';
}

class TraktSettingsNotifier extends AsyncNotifier<TraktState>
    with SettingsPersistence {
  static const _keyAccessToken = TraktTokenStore.keyAccessToken;
  static const _keyRefreshToken = TraktTokenStore.keyRefreshToken;
  static const _keyExpiresIn = TraktTokenStore.keyExpiresIn;
  static const _keyCreatedAt = TraktTokenStore.keyCreatedAt;
  static const _keyUsername = 'smplayer.trakt_username';

  @override
  Future<TraktState> build() async {
    ref.read(traktAuthProvider).onCleared = () {
      final s = state.value;
      if (s != null && s.isConnected) {
        state = const AsyncValue.data(TraktState());
      }
    };

    final prefs = await getPrefs();
    final accessToken = prefs.getString(_keyAccessToken)?.trim() ?? '';
    return TraktState(
      isConnected: accessToken.isNotEmpty,
      username: prefs.getString(_keyUsername) ?? '',
      accessToken: accessToken,
      refreshToken: prefs.getString(_keyRefreshToken)?.trim() ?? '',
      expiresIn: prefs.getInt(_keyExpiresIn) ?? 0,
      createdAt: prefs.getInt(_keyCreatedAt) ?? 0,
    );
  }


  Future<void> connect() async {
    final gateway = ref.read(traktGatewayProvider);

    final csrfState = _generateState();

    final redirectFuture = DeepLinkService.instance.uriStream
        .firstWhere((uri) => uri.queryParameters['state'] == csrfState)
        .timeout(_connectTimeout);

    final authorizeUrl = TraktCredentials.authorizeUrl(csrfState);
    final launched = await launchUrl(
      authorizeUrl,
      mode: LaunchMode.externalApplication,
    );
    if (!launched) {
      throw const TraktConnectException('无法打开浏览器');
    }

    final Uri redirect;
    try {
      redirect = await redirectFuture;
    } on TimeoutException {
      throw const TraktConnectException('连接超时，请重试');
    }

    final error = redirect.queryParameters['error'];
    if (error != null && error.isNotEmpty) {
      AppLogger.warn(_tag, 'authorization denied: $error');
      throw const TraktConnectException('已取消授权');
    }

    final returnedState = redirect.queryParameters['state'];
    if (returnedState != csrfState) {
      AppLogger.warn(_tag, 'state mismatch');
      throw const TraktConnectException('授权校验失败');
    }

    final code = redirect.queryParameters['code'];
    if (code == null || code.isEmpty) {
      throw const TraktConnectException('未收到授权码');
    }

    final TraktToken token;
    try {
      token = await gateway.exchangeCode(code);
    } catch (e) {
      AppLogger.warn(_tag, 'exchangeCode failed', e);
      throw const TraktConnectException('令牌交换失败');
    }

    await _persistToken(token);

    var username = '';
    try {
      final user = await gateway.getMe(token.accessToken);
      username = user.username;
    } catch (e) {
      AppLogger.warn(_tag, 'getMe failed (non-fatal)', e);
    }
    await persistField(_keyUsername, username);

    state = AsyncValue.data(
      TraktState(
        isConnected: true,
        username: username,
        accessToken: token.accessToken,
        refreshToken: token.refreshToken,
        expiresIn: token.expiresIn,
        createdAt: token.createdAt,
      ),
    );
  }


  Future<void> disconnect() async {
    await ref.read(traktAuthProvider).clear();
    await persistField(_keyUsername, null);
    state = const AsyncValue.data(TraktState());
  }


  Future<String?> ensureValidToken() {
    return ref.read(traktAuthProvider).validAccessToken();
  }

  Future<void> _persistToken(TraktToken token) async {
    await persistFields(<String, dynamic>{
      _keyAccessToken: token.accessToken,
      _keyRefreshToken: token.refreshToken,
      _keyExpiresIn: token.expiresIn,
      _keyCreatedAt: token.createdAt,
    });
  }

  static String _generateState() {
    final rnd = Random.secure();
    final bytes = List<int>.generate(16, (_) => rnd.nextInt(256));
    return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
  }
}

final traktSettingsProvider =
    AsyncNotifierProvider<TraktSettingsNotifier, TraktState>(
      TraktSettingsNotifier.new,
    );
