import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../core/domain/ports/sm_account_gateway.dart';
import '../core/infra/sm_account_api/sm_account_client.dart';
import 'shared/settings_persistence.dart';
import 'tmdb_settings_provider.dart';


final smAccountGatewayProvider = Provider<SmAccountGateway>(
  (ref) => SmAccountClient(),
);


enum SmAuthStatus {

  unauthenticated,


  restoring,


  authenticated,


  expired,


  degraded,
}


class SmAccountState {

  final SmAuthStatus status;


  final String email;


  final String baseUrl;


  final String dataRefreshToken;


  final bool isVip;


  final int? vipExpiresAt;


  final bool vipPermanent;

  const SmAccountState({
    this.status = SmAuthStatus.unauthenticated,
    this.email = '',
    this.baseUrl = defaultBaseUrl,
    this.dataRefreshToken = '',
    this.isVip = false,
    this.vipExpiresAt,
    this.vipPermanent = false,
  });


  static const defaultBaseUrl = 'https://your-server.example';


  String get tmdbApiBaseUrl => '${baseUrl.replaceAll(RegExp(r'/+$'), '')}/3';


  bool get loggedIn => status == SmAuthStatus.authenticated;


  bool get canUseDataPlane => status == SmAuthStatus.authenticated;


  bool get hasActiveSession =>
      status == SmAuthStatus.authenticated || status == SmAuthStatus.restoring;


  bool get needsReauth =>
      status == SmAuthStatus.expired || status == SmAuthStatus.degraded;


  bool get hasVip => status == SmAuthStatus.authenticated && isVip;

  SmAccountState copyWith({
    SmAuthStatus? status,
    String? email,
    String? baseUrl,
    String? dataRefreshToken,
    bool? isVip,
    int? vipExpiresAt,
    bool? vipPermanent,
    bool clearVipExpiresAt = false,
  }) => SmAccountState(
    status: status ?? this.status,
    email: email ?? this.email,
    baseUrl: baseUrl ?? this.baseUrl,
    dataRefreshToken: dataRefreshToken ?? this.dataRefreshToken,
    isVip: isVip ?? this.isVip,
    vipExpiresAt: clearVipExpiresAt
        ? null
        : (vipExpiresAt ?? this.vipExpiresAt),
    vipPermanent: vipPermanent ?? this.vipPermanent,
  );
}

class SmAccountNotifier extends AsyncNotifier<SmAccountState>
    with SettingsPersistence {
  static const _keyBaseUrl = 'smplayer.sm_account_base_url';
  static const _keyEmail = 'smplayer.sm_account_email';


  static const _keyCookie = 'smplayer.sm_account_cookie';
  static const _keyRefreshToken = 'smplayer.sm_account_refresh_token';
  static const _keyVip = 'smplayer.sm_account_vip';
  static const _keyVipExpiresAt = 'smplayer.sm_account_vip_expires_at';
  static const _keyVipPermanent = 'smplayer.sm_account_vip_permanent';


  String? _accessToken;
  DateTime? _accessTokenExpiry;
  Future<String?>? _refreshInFlight;

  @override
  Future<SmAccountState> build() async {
    final prefs = await getPrefs();
    final refreshToken = prefs.getString(_keyRefreshToken)?.trim() ?? '';
    final legacyCookie = prefs.getString(_keyCookie)?.trim() ?? '';
    final rawBaseUrl = prefs.getString(_keyBaseUrl)?.trim();
    final baseUrl = (rawBaseUrl == null || rawBaseUrl.isEmpty)
        ? SmAccountState.defaultBaseUrl
        : rawBaseUrl;
    final email = prefs.getString(_keyEmail)?.trim() ?? '';
    final isVip = prefs.getBool(_keyVip) ?? false;
    final vipExpiresAt = prefs.getInt(_keyVipExpiresAt);
    final vipPermanent = prefs.getBool(_keyVipPermanent) ?? false;

    if (legacyCookie.isNotEmpty) {
      await persistField(_keyCookie, null);
    }

    if (refreshToken.isNotEmpty) {
      Future.microtask(_restoreSession);
      return SmAccountState(
        status: SmAuthStatus.restoring,
        email: email,
        baseUrl: baseUrl,
        dataRefreshToken: refreshToken,
        isVip: isVip,
        vipExpiresAt: vipExpiresAt,
        vipPermanent: vipPermanent,
      );
    }

    if (legacyCookie.isNotEmpty) {
      return SmAccountState(
        status: SmAuthStatus.degraded,
        email: email,
        baseUrl: baseUrl,
      );
    }

    return SmAccountState(
      status: SmAuthStatus.unauthenticated,
      baseUrl: baseUrl,
    );
  }


  Future<void> _restoreSession() async {
    final current = state.value;
    if (current == null ||
        current.status != SmAuthStatus.restoring ||
        current.dataRefreshToken.isEmpty) {
      return;
    }
    final token = await getAccessToken(force: true);
    if (token != null) {
      return;
    }
    final after = state.value;
    if (after != null &&
        after.status == SmAuthStatus.restoring &&
        after.dataRefreshToken.isNotEmpty) {
      _setStatus(SmAuthStatus.authenticated);
    }
  }

  void _setStatus(SmAuthStatus status) {
    final current = state.value;
    if (current == null || current.status == status) return;
    state = AsyncValue.data(current.copyWith(status: status));
  }


  Future<String?> getAccessToken({bool force = false}) async {
    final current = state.value;
    if (current == null || current.dataRefreshToken.isEmpty) return null;

    if (!force &&
        _accessToken != null &&
        _accessTokenExpiry != null &&
        _accessTokenExpiry!.isAfter(
          DateTime.now().add(const Duration(seconds: 60)),
        )) {
      return _accessToken;
    }

    final inFlight = _refreshInFlight;
    if (inFlight != null) return inFlight;

    final refresh = _refreshAccessToken(current);
    _refreshInFlight = refresh;
    try {
      return await refresh;
    } finally {
      if (identical(_refreshInFlight, refresh)) {
        _refreshInFlight = null;
      }
    }
  }

  Future<String?> _refreshAccessToken(SmAccountState current) async {
    final result = await ref
        .read(smAccountGatewayProvider)
        .refreshAccessToken(
          baseUrl: current.baseUrl,
          refreshToken: current.dataRefreshToken,
        );

    if (!result.ok) {
      _accessToken = null;
      _accessTokenExpiry = null;
      if (result.invalidRefreshToken) {
        await _markExpired();
      }
      return null;
    }

    _accessToken = result.accessToken;
    _accessTokenExpiry = DateTime.now().add(
      Duration(seconds: result.expiresIn > 0 ? result.expiresIn : 900),
    );

    final latest = state.value ?? current;
    final rotatedRefreshToken = result.refreshToken.trim();
    final nextRefreshToken =
        (rotatedRefreshToken.isNotEmpty &&
            rotatedRefreshToken != latest.dataRefreshToken)
        ? rotatedRefreshToken
        : latest.dataRefreshToken;
    if (nextRefreshToken != latest.dataRefreshToken) {
      await persistField(_keyRefreshToken, nextRefreshToken);
    }
    if (result.hasVipInfo) {
      await persistFields(<String, dynamic>{
        _keyVip: result.isVip,
        _keyVipExpiresAt: result.vipExpiresAt,
        _keyVipPermanent: result.vipPermanent,
      });
    }
    state = AsyncValue.data(
      latest.copyWith(
        status: SmAuthStatus.authenticated,
        dataRefreshToken: nextRefreshToken,
        isVip: result.hasVipInfo ? result.isVip : null,
        vipExpiresAt: result.hasVipInfo ? result.vipExpiresAt : null,
        clearVipExpiresAt: result.hasVipInfo && result.vipExpiresAt == null,
        vipPermanent: result.hasVipInfo ? result.vipPermanent : null,
      ),
    );
    return _accessToken;
  }


  Future<SmLoginResult> login(String email, String password) async {
    final current = state.value ?? const SmAccountState();
    final baseUrl = current.baseUrl.trim().isEmpty
        ? SmAccountState.defaultBaseUrl
        : current.baseUrl.trim();

    final result = await ref
        .read(smAccountGatewayProvider)
        .login(baseUrl: baseUrl, email: email.trim(), password: password);
    if (!result.ok) return result;

    await _applySession(baseUrl, result);
    return result;
  }


  Future<SmActionResult> registerStart(String email) async {
    final baseUrl = _effectiveBaseUrl();
    final trimmedEmail = email.trim();
    final result = await ref
        .read(smAccountGatewayProvider)
        .registerStart(baseUrl: baseUrl, email: trimmedEmail);
    if (result.ok) {
      await persistField(_keyBaseUrl, baseUrl);
    }
    return result;
  }


  Future<SmLoginResult> registerVerify(
    String email,
    String password,
    String code,
  ) async {
    final baseUrl = _effectiveBaseUrl();
    final result = await ref
        .read(smAccountGatewayProvider)
        .registerVerify(
          baseUrl: baseUrl,
          email: email.trim(),
          password: password,
          code: code.trim(),
        );
    if (!result.ok) return result;

    await _applySession(baseUrl, result);
    return result;
  }


  Future<SmActionResult> resendRegisterCode(String email) async {
    return ref
        .read(smAccountGatewayProvider)
        .resendCode(baseUrl: _effectiveBaseUrl(), email: email.trim());
  }


  Future<void> logout() async {
    final current = state.value ?? const SmAccountState();
    _accessToken = null;
    _accessTokenExpiry = null;
    _refreshInFlight = null;
    if (current.dataRefreshToken.isNotEmpty) {
      await ref
          .read(smAccountGatewayProvider)
          .logout(
            baseUrl: current.baseUrl,
            refreshToken: current.dataRefreshToken,
          );
    }
    await persistFields(<String, dynamic>{
      _keyEmail: null,
      _keyCookie: null,
      _keyRefreshToken: null,
      _keyVip: null,
      _keyVipExpiresAt: null,
      _keyVipPermanent: null,
    });
    state = AsyncValue.data(
      SmAccountState(
        status: SmAuthStatus.unauthenticated,
        baseUrl: current.baseUrl,
      ),
    );
  }


  Future<void> _applySession(String baseUrl, SmLoginResult result) async {
    _accessToken = result.accessToken.isNotEmpty ? result.accessToken : null;
    _accessTokenExpiry = result.expiresIn > 0
        ? DateTime.now().add(Duration(seconds: result.expiresIn))
        : null;
    _refreshInFlight = null;

    await persistFields(<String, dynamic>{
      _keyBaseUrl: baseUrl,
      _keyEmail: result.email,
      _keyRefreshToken: result.refreshToken,
      _keyVip: result.isVip,
      _keyVipExpiresAt: result.vipExpiresAt,
      _keyVipPermanent: result.vipPermanent,
      _keyCookie: null,
    });
    final current = state.value ?? const SmAccountState();
    state = AsyncValue.data(
      current.copyWith(
        status: SmAuthStatus.authenticated,
        email: result.email,
        baseUrl: baseUrl,
        dataRefreshToken: result.refreshToken,
        isVip: result.isVip,
        vipExpiresAt: result.vipExpiresAt,
        clearVipExpiresAt: result.vipExpiresAt == null,
        vipPermanent: result.vipPermanent,
      ),
    );
    if (result.isVip) {
      await ref.read(tmdbSettingsProvider.notifier).enableSmAccountProxy();
    }
  }


  Future<void> _markExpired() async {
    _accessToken = null;
    _accessTokenExpiry = null;
    _refreshInFlight = null;
    await persistFields(<String, dynamic>{
      _keyRefreshToken: null,
      _keyVip: null,
      _keyVipExpiresAt: null,
      _keyVipPermanent: null,
    });
    final current = state.value ?? const SmAccountState();
    state = AsyncValue.data(
      current.copyWith(
        status: SmAuthStatus.expired,
        dataRefreshToken: '',
        isVip: false,
        clearVipExpiresAt: true,
        vipPermanent: false,
      ),
    );
  }

  String _effectiveBaseUrl() {
    final current = state.value ?? const SmAccountState();
    final v = current.baseUrl.trim();
    return v.isEmpty ? SmAccountState.defaultBaseUrl : v;
  }
}

final smAccountProvider =
    AsyncNotifierProvider<SmAccountNotifier, SmAccountState>(
      SmAccountNotifier.new,
    );
