import 'dart:convert';

import '../../contracts/auth.dart';
import '../../contracts/backup.dart';
import '../../contracts/error.dart';
import '../../contracts/script_widget.dart';
import '../../contracts/server.dart';
import '../errors.dart';
import '../ports/danmaku_sources_store.dart';
import '../ports/emby_gateway.dart';
import '../ports/script_widget_repository.dart';
import '../ports/server_repository.dart';
import '../ports/server_sync_gateway.dart';
import '../ports/session_repository.dart';
import 'backup_usecases.dart';


class ServerSyncUploadResult {
  final int serverCount;
  final int sessionCount;
  final int danmakuSourceCount;
  final int scriptWidgetCount;
  final int scriptWidgetSubscriptionCount;

  const ServerSyncUploadResult({
    required this.serverCount,
    required this.sessionCount,
    required this.danmakuSourceCount,
    required this.scriptWidgetCount,
    required this.scriptWidgetSubscriptionCount,
  });
}


class ServerSyncPullResult {
  final bool empty;
  final int serverCount;
  final int sessionCount;
  final int danmakuSourceCount;
  final int scriptWidgetCount;
  final int scriptWidgetSubscriptionCount;

  const ServerSyncPullResult({
    required this.empty,
    required this.serverCount,
    required this.sessionCount,
    required this.danmakuSourceCount,
    required this.scriptWidgetCount,
    required this.scriptWidgetSubscriptionCount,
  });
}


class UploadServerSyncUseCase {
  final ExportBackupUseCase export;
  final ServerSyncGateway gateway;

  UploadServerSyncUseCase({required this.export, required this.gateway});

  Future<ServerSyncUploadResult> execute({
    required String baseUrl,
    required String accessToken,
  }) async {
    final exported = await export.execute();
    await gateway.push(
      baseUrl: baseUrl,
      accessToken: accessToken,
      blob: exported.json,
    );
    return ServerSyncUploadResult(
      serverCount: exported.serverCount,
      sessionCount: exported.sessionCount,
      danmakuSourceCount: exported.danmakuSourceCount,
      scriptWidgetCount: exported.scriptWidgetCount,
      scriptWidgetSubscriptionCount: exported.scriptWidgetSubscriptionCount,
    );
  }
}


class PullServerSyncUseCase {
  final ServerRepository serverRepository;
  final SessionRepository sessionRepository;
  final DanmakuSourcesStore danmakuSourcesStore;
  final ScriptWidgetRepository scriptWidgetRepository;
  final ServerSyncGateway gateway;

  PullServerSyncUseCase({
    required this.serverRepository,
    required this.sessionRepository,
    required this.danmakuSourcesStore,
    required this.scriptWidgetRepository,
    required this.gateway,
  });

  Future<ServerSyncPullResult> execute({
    required String baseUrl,
    required String accessToken,
  }) async {
    final snapshot = await gateway.pull(
      baseUrl: baseUrl,
      accessToken: accessToken,
    );
    if (snapshot.isEmpty) {
      return const ServerSyncPullResult(
        empty: true,
        serverCount: 0,
        sessionCount: 0,
        danmakuSourceCount: 0,
        scriptWidgetCount: 0,
        scriptWidgetSubscriptionCount: 0,
      );
    }

    final bundle = _parseBundle(snapshot.blob!);
    final prevActive = await sessionRepository.getActiveServerId();
    final oldServers = await serverRepository.list();
    final oldSessions = await sessionRepository.listAll();
    final oldScriptWidgets = await scriptWidgetRepository.listWidgets();
    final oldScriptWidgetSubscriptions =
        await scriptWidgetRepository.listSubscriptions();

    final keepActive =
        prevActive != null &&
        bundle.sessions.any((s) => s.serverId == prevActive);
    final nextActive = keepActive
        ? prevActive
        : (bundle.sessions.isNotEmpty ? bundle.sessions.first.serverId : null);

    try {
      await serverRepository.replaceAll(bundle.servers);
      await sessionRepository.replaceAll(
        bundle.sessions,
        activeServerId: nextActive,
      );
    } catch (_) {
      await _restoreLocalSnapshot(
        servers: oldServers,
        sessions: oldSessions,
        activeServerId: prevActive,
      );
      rethrow;
    }

    final mergedSources = mergeDanmakuSourcesByUrl(
      local: await danmakuSourcesStore.list(),
      remote: bundle.danmakuSources,
    );
    await danmakuSourcesStore.replaceAll(mergedSources);

    try {
      await scriptWidgetRepository.replaceAllWidgets(bundle.scriptWidgets);
      await scriptWidgetRepository.replaceAllSubscriptions(
        bundle.scriptWidgetSubscriptions,
      );
    } catch (_) {
      await _restoreScriptWidgets(
        widgets: oldScriptWidgets,
        subscriptions: oldScriptWidgetSubscriptions,
      );
      rethrow;
    }

    return ServerSyncPullResult(
      empty: false,
      serverCount: bundle.servers.length,
      sessionCount: bundle.sessions.length,
      danmakuSourceCount: mergedSources.length,
      scriptWidgetCount: bundle.scriptWidgets.length,
      scriptWidgetSubscriptionCount: bundle.scriptWidgetSubscriptions.length,
    );
  }

  BackupBundle _parseBundle(String rawJson) {
    final BackupBundle bundle;
    try {
      final decoded = jsonDecode(rawJson);
      if (decoded is! Map<String, dynamic>) {
        throw DomainError(ErrorCode.invalidParams, '云端同步数据格式不正确');
      }
      bundle = BackupBundle.fromJson(decoded);
    } on DomainError {
      rethrow;
    } catch (e) {
      throw DomainError(
        ErrorCode.invalidParams,
        '云端同步数据解析失败',
        details: {'reason': e.toString()},
      );
    }
    if (bundle.version != kBackupSchemaVersion) {
      throw DomainError(
        ErrorCode.invalidParams,
        '不支持的同步数据版本：${bundle.version}',
        details: {'expected': kBackupSchemaVersion, 'got': bundle.version},
      );
    }
    return bundle;
  }

  Future<void> _restoreLocalSnapshot({
    required List<EmbyServer> servers,
    required List<SessionData> sessions,
    required String? activeServerId,
  }) async {
    try {
      await serverRepository.replaceAll(servers);
      await sessionRepository.replaceAll(
        sessions,
        activeServerId: activeServerId,
      );
    } catch (_) {}
  }

  Future<void> _restoreScriptWidgets({
    required List<InstalledScriptWidget> widgets,
    required List<ScriptWidgetSubscription> subscriptions,
  }) async {
    try {
      await scriptWidgetRepository.replaceAllWidgets(widgets);
      await scriptWidgetRepository.replaceAllSubscriptions(subscriptions);
    } catch (_) {}
  }
}


class RepairSessionsUseCase {
  final ServerRepository serverRepository;
  final SessionRepository sessionRepository;
  final EmbyGateway embyGateway;

  RepairSessionsUseCase({
    required this.serverRepository,
    required this.sessionRepository,
    required this.embyGateway,
  });

  Future<int> execute() async {
    final sessions = await sessionRepository.listAll();
    var repaired = 0;
    for (final session in sessions) {
      final server = await serverRepository.findById(session.serverId);
      if (server == null) continue;
      final ctx = AuthedRequestContext(
        baseUrl: server.baseUrl,
        token: session.token,
        userId: session.userId,
      );
      try {
        await embyGateway.getItemCounts(ctx);
      } on DomainError catch (e) {
        if (e.code != ErrorCode.authInvalidCredentials &&
            e.code != ErrorCode.authForbidden) {
          continue;
        }
        if (await _reauth(server.baseUrl, session)) repaired++;
      } catch (_) {
        continue;
      }
    }
    return repaired;
  }

  Future<bool> _reauth(String baseUrl, SessionData session) async {
    final pwd = session.password;
    if (pwd == null || pwd.isEmpty) return false;
    try {
      final auth = await embyGateway.authenticateByName(
        baseUrl: baseUrl,
        username: session.userName,
        password: pwd,
      );
      await sessionRepository.setMany([
        session.copyWith(token: auth.accessToken, userId: auth.userId),
      ]);
      return true;
    } catch (_) {
      return false;
    }
  }
}
