import 'dart:async';
import 'dart:convert';
import 'dart:io';

import '../../../shared/utils/async_lock.dart';
import '../../contracts/auth.dart';
import '../../domain/ports/session_repository.dart';

class _SessionStoreData {
  List<SessionData> sessions;
  String? activeServerId;

  _SessionStoreData({required this.sessions, this.activeServerId});

  factory _SessionStoreData.empty() =>
      _SessionStoreData(sessions: <SessionData>[]);

  factory _SessionStoreData.fromJson(Map<String, dynamic> json) =>
      _SessionStoreData(
        sessions: ((json['sessions'] as List<dynamic>?) ?? <dynamic>[])
            .map((e) => SessionData.fromJson(e as Map<String, dynamic>))
            .toList(),
        activeServerId: json['activeServerId'] as String?,
      );

  Map<String, dynamic> toJson() => {
    'sessions': sessions.map((e) => e.toJson()).toList(),
    'activeServerId': activeServerId,
  };
}

class JsonSessionRepository implements SessionRepository {
  final File _file;
  final _lock = AsyncLock();

  JsonSessionRepository({required this._file});

  Future<_SessionStoreData> _read() async {
    if (!await _file.exists()) return _SessionStoreData.empty();
    final raw = await _file.readAsString();
    if (raw.trim().isEmpty) return _SessionStoreData.empty();
    try {
      return _SessionStoreData.fromJson(
        jsonDecode(raw) as Map<String, dynamic>,
      );
    } catch (_) {
      return _SessionStoreData.empty();
    }
  }

  Future<void> _write(_SessionStoreData data) async {
    if (!await _file.parent.exists()) {
      await _file.parent.create(recursive: true);
    }
    await _file.writeAsString(
      const JsonEncoder.withIndent('  ').convert(data.toJson()),
      flush: true,
    );
  }

  @override
  Future<SessionData> set(SessionData session) async {
    return _lock.run(() async {
      final data = await _read();
      data.sessions.removeWhere((e) => e.serverId == session.serverId);
      data.sessions.add(session);
      data.activeServerId = session.serverId;
      await _write(data);
      return session;
    });
  }

  @override
  Future<void> setMany(List<SessionData> sessions) async {
    if (sessions.isEmpty) return;
    await _lock.run(() async {
      final data = await _read();
      for (final session in sessions) {
        data.sessions.removeWhere((e) => e.serverId == session.serverId);
        data.sessions.add(session);
      }
      await _write(data);
    });
  }

  @override
  Future<void> replaceAll(
    List<SessionData> sessions, {
    String? activeServerId,
  }) async {
    await _lock.run(() async {
      await _write(
        _SessionStoreData(
          sessions: List<SessionData>.from(sessions),
          activeServerId: activeServerId,
        ),
      );
    });
  }

  @override
  Future<SessionData?> getByServerId(String serverId) async {
    final data = await _lock.run(_read);
    for (final s in data.sessions) {
      if (s.serverId == serverId) return s;
    }
    return null;
  }

  @override
  Future<SessionData?> getActive() async {
    final data = await _lock.run(_read);
    if (data.activeServerId == null) {
      return data.sessions.isEmpty ? null : data.sessions.first;
    }
    for (final s in data.sessions) {
      if (s.serverId == data.activeServerId) return s;
    }
    return data.sessions.isEmpty ? null : data.sessions.first;
  }

  @override
  Future<String?> getActiveServerId() async {
    final data = await _lock.run(_read);
    return data.activeServerId;
  }

  @override
  Future<List<SessionData>> listAll() async {
    final data = await _lock.run(_read);
    return List<SessionData>.from(data.sessions);
  }

  @override
  Future<void> setActive(String serverId) async {
    await _lock.run(() async {
      final data = await _read();
      data.activeServerId = serverId;
      await _write(data);
    });
  }

  @override
  Future<void> clear([String? serverId]) async {
    await _lock.run(() async {
      final data = await _read();
      if (serverId == null) {
        data.sessions.clear();
        data.activeServerId = null;
      } else {
        data.sessions.removeWhere((e) => e.serverId == serverId);
        if (data.activeServerId == serverId) {
          data.activeServerId = data.sessions.isEmpty
              ? null
              : data.sessions.first.serverId;
        }
      }
      await _write(data);
    });
  }
}
