import 'dart:convert';

import '../../contracts/contracts.dart';
import '../errors.dart';
import '../ports/danmaku_sources_store.dart';
import '../ports/script_widget_repository.dart';
import '../ports/server_repository.dart';
import '../ports/session_repository.dart';

const int kBackupSchemaVersion = 1;

class BackupExportResult {
  final String json;
  final int serverCount;
  final int sessionCount;
  final int danmakuSourceCount;
  final int scriptWidgetCount;
  final int scriptWidgetSubscriptionCount;

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

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

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

class ExportBackupUseCase {
  final ServerRepository serverRepository;
  final SessionRepository sessionRepository;
  final DanmakuSourcesStore danmakuSourcesStore;
  final ScriptWidgetRepository scriptWidgetRepository;
  final DateTime Function() now;

  ExportBackupUseCase({
    required this.serverRepository,
    required this.sessionRepository,
    required this.danmakuSourcesStore,
    required this.scriptWidgetRepository,
    DateTime Function()? now,
  }) : now = (now ?? DateTime.now);

  Future<BackupExportResult> execute() async {
    final servers = await serverRepository.list();
    final sessions = await sessionRepository.listAll();
    final danmakuSources = await danmakuSourcesStore.list();
    final scriptWidgets = await scriptWidgetRepository.listWidgets();
    final scriptWidgetSubscriptions =
        await scriptWidgetRepository.listSubscriptions();
    final bundle = BackupBundle(
      version: kBackupSchemaVersion,
      exportedAt: now().toUtc().toIso8601String(),
      servers: servers,
      sessions: sessions,
      danmakuSources: danmakuSources,
      scriptWidgets: scriptWidgets,
      scriptWidgetSubscriptions: scriptWidgetSubscriptions,
    );
    final json = const JsonEncoder.withIndent('  ').convert(bundle.toJson());
    return BackupExportResult(
      json: json,
      serverCount: servers.length,
      sessionCount: sessions.length,
      danmakuSourceCount: danmakuSources.length,
      scriptWidgetCount: scriptWidgets.length,
      scriptWidgetSubscriptionCount: scriptWidgetSubscriptions.length,
    );
  }
}

class ImportBackupUseCase {
  final ServerRepository serverRepository;
  final SessionRepository sessionRepository;
  final DanmakuSourcesStore danmakuSourcesStore;
  final ScriptWidgetRepository scriptWidgetRepository;

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

  Future<BackupImportResult> execute(String rawJson) async {
    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},
      );
    }

    for (final server in bundle.servers) {
      await serverRepository.update(server);
    }
    await sessionRepository.setMany(bundle.sessions);

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

    for (final scriptWidget in bundle.scriptWidgets) {
      await scriptWidgetRepository.upsertWidget(scriptWidget);
    }
    for (final subscription in bundle.scriptWidgetSubscriptions) {
      await scriptWidgetRepository.upsertSubscription(subscription);
    }

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


List<DanmakuSource> mergeDanmakuSourcesByUrl({
  required List<DanmakuSource> local,
  required List<DanmakuSource> remote,
}) {
  final localByUrl = <String, DanmakuSource>{
    for (final s in local) s.url.trim(): s,
  };
  final out = <DanmakuSource>[];
  final seen = <String>{};
  for (final r in remote) {
    final url = r.url.trim();
    if (url.isEmpty || seen.contains(url)) continue;
    seen.add(url);
    final hit = localByUrl[url];
    if (hit != null) {
      out.add(hit.copyWith(name: hit.name.trim().isEmpty ? r.name : hit.name));
    } else {
      out.add(
        DanmakuSource(
          id: r.id.trim().isEmpty
              ? 'src_${url.hashCode.toUnsigned(32).toRadixString(16)}'
              : r.id,
          name: r.name,
          url: url,
        ),
      );
    }
  }
  return out;
}
