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

import '../../../shared/utils/async_lock.dart';
import '../../contracts/script_widget.dart';
import '../../domain/ports/script_widget_repository.dart';

class JsonScriptWidgetRepository implements ScriptWidgetRepository {
  final File file;
  final AsyncLock _lock = AsyncLock();

  JsonScriptWidgetRepository({required this.file});

  Future<_ScriptWidgetRepositoryState> _read() async {
    if (!await file.exists()) return const _ScriptWidgetRepositoryState();
    final rawJson = await file.readAsString();
    if (rawJson.trim().isEmpty) return const _ScriptWidgetRepositoryState();
    try {
      final decoded = jsonDecode(rawJson);
      if (decoded is! Map<String, dynamic>) {
        return const _ScriptWidgetRepositoryState();
      }
      return _ScriptWidgetRepositoryState.fromJson(decoded);
    } catch (_) {
      return const _ScriptWidgetRepositoryState();
    }
  }

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

  @override
  Future<List<InstalledScriptWidget>> listWidgets() => _lock.run(() async {
    final state = await _read();
    final widgets = List<InstalledScriptWidget>.from(state.widgets);
    widgets.sort(
      (left, right) => left.manifest.title.compareTo(right.manifest.title),
    );
    return widgets;
  });

  @override
  Future<InstalledScriptWidget?> findWidget(String widgetId) async {
    final widgets = await listWidgets();
    for (final widget in widgets) {
      if (widget.manifest.id == widgetId) return widget;
    }
    return null;
  }

  @override
  Future<void> upsertWidget(InstalledScriptWidget widget) =>
      _lock.run(() async {
        final state = await _read();
        final widgets = List<InstalledScriptWidget>.from(state.widgets);
        final existingIndex = widgets.indexWhere(
          (candidate) => candidate.manifest.id == widget.manifest.id,
        );
        if (existingIndex < 0) {
          widgets.add(widget);
        } else {
          widgets[existingIndex] = widget;
        }
        await _write(state.copyWith(widgets: widgets));
      });

  @override
  Future<void> deleteWidget(String widgetId) => _lock.run(() async {
    final state = await _read();
    final widgets = List<InstalledScriptWidget>.from(state.widgets)
      ..removeWhere((widget) => widget.manifest.id == widgetId);
    await _write(state.copyWith(widgets: widgets));
  });

  @override
  Future<List<ScriptWidgetSubscription>> listSubscriptions() =>
      _lock.run(() async {
        final state = await _read();
        return List<ScriptWidgetSubscription>.unmodifiable(state.subscriptions);
      });

  @override
  Future<void> upsertSubscription(ScriptWidgetSubscription subscription) =>
      _lock.run(() async {
        final state = await _read();
        final subscriptions = List<ScriptWidgetSubscription>.from(
          state.subscriptions,
        );
        final existingIndex = subscriptions.indexWhere(
          (candidate) => candidate.url == subscription.url,
        );
        if (existingIndex < 0) {
          subscriptions.add(subscription);
        } else {
          subscriptions[existingIndex] = subscription;
        }
        await _write(state.copyWith(subscriptions: subscriptions));
      });

  @override
  Future<void> replaceAllWidgets(List<InstalledScriptWidget> widgets) =>
      _lock.run(() async {
        final state = await _read();
        await _write(
          state.copyWith(widgets: List<InstalledScriptWidget>.from(widgets)),
        );
      });

  @override
  Future<void> replaceAllSubscriptions(
    List<ScriptWidgetSubscription> subscriptions,
  ) => _lock.run(() async {
    final state = await _read();
    await _write(
      state.copyWith(
        subscriptions: List<ScriptWidgetSubscription>.from(subscriptions),
      ),
    );
  });
}

class _ScriptWidgetRepositoryState {
  final List<InstalledScriptWidget> widgets;
  final List<ScriptWidgetSubscription> subscriptions;

  const _ScriptWidgetRepositoryState({
    this.widgets = const <InstalledScriptWidget>[],
    this.subscriptions = const <ScriptWidgetSubscription>[],
  });

  factory _ScriptWidgetRepositoryState.fromJson(Map<String, dynamic> json) {
    return _ScriptWidgetRepositoryState(
      widgets: _decodeList(json['widgets'], InstalledScriptWidget.fromJson),
      subscriptions: _decodeList(
        json['subscriptions'],
        ScriptWidgetSubscription.fromJson,
      ),
    );
  }

  _ScriptWidgetRepositoryState copyWith({
    List<InstalledScriptWidget>? widgets,
    List<ScriptWidgetSubscription>? subscriptions,
  }) {
    return _ScriptWidgetRepositoryState(
      widgets: widgets ?? this.widgets,
      subscriptions: subscriptions ?? this.subscriptions,
    );
  }

  Map<String, dynamic> toJson() => <String, dynamic>{
    'widgets': widgets.map((widget) => widget.toJson()).toList(),
    'subscriptions': subscriptions
        .map((subscription) => subscription.toJson())
        .toList(),
  };
}

List<T> _decodeList<T>(
  Object? rawValue,
  T Function(Map<String, dynamic>) fromJson,
) {
  if (rawValue is! List) return <T>[];
  final decodedItems = <T>[];
  for (final rawItem in rawValue) {
    if (rawItem is! Map) continue;
    try {
      decodedItems.add(fromJson(Map<String, dynamic>.from(rawItem)));
    } catch (_) {}
  }
  return decodedItems;
}
