import 'locale_storage_backend.dart';
import 'locale_storage_policy.dart';

enum _StorageTarget { local, session, memory }

abstract interface class LocaleStorageTarget {
  String? read(String key);

  bool write(String key, String value);

  bool remove(String key);

  void clear();
}

class LocaleStorageNoopTarget implements LocaleStorageTarget {
  const LocaleStorageNoopTarget();

  @override
  String? read(String key) => null;

  @override
  bool write(String key, String value) => false;

  @override
  bool remove(String key) => false;

  @override
  void clear() {}
}

class LocaleStorageCallbackTarget implements LocaleStorageTarget {
  LocaleStorageCallbackTarget({
    required this.readCallback,
    required this.writeCallback,
    required this.removeCallback,
    required this.clearCallback,
  });

  final String? Function(String key) readCallback;
  final void Function(String key, String value) writeCallback;
  final void Function(String key) removeCallback;
  final void Function() clearCallback;

  @override
  String? read(String key) => readCallback(key);

  @override
  bool write(String key, String value) {
    writeCallback(key, value);
    return true;
  }

  @override
  bool remove(String key) {
    removeCallback(key);
    return true;
  }

  @override
  void clear() => clearCallback();
}

class LocaleStorageEngine implements LocaleStorageBackend {
  LocaleStorageEngine({
    required LocaleStorageTarget localTarget,
    required LocaleStorageTarget sessionTarget,
  }) : _localTarget = localTarget,
       _sessionTarget = sessionTarget;

  final LocaleStorageTarget _localTarget;
  final LocaleStorageTarget _sessionTarget;
  final Map<String, String> _memory = {};
  LocaleStorageTestMode _mode = LocaleStorageTestMode.normal;

  List<_StorageTarget> _fallbackTargets() {
    switch (_mode) {
      case LocaleStorageTestMode.normal:
        return [
          _StorageTarget.local,
          _StorageTarget.session,
          _StorageTarget.memory,
        ];
      case LocaleStorageTestMode.sessionOnly:
        return [_StorageTarget.session, _StorageTarget.memory];
      case LocaleStorageTestMode.memoryOnly:
        return [_StorageTarget.memory];
    }
  }

  String? _safeReadTarget(_StorageTarget target, String key) {
    try {
      switch (target) {
        case _StorageTarget.local:
          return _localTarget.read(key);
        case _StorageTarget.session:
          return _sessionTarget.read(key);
        case _StorageTarget.memory:
          return _memory[key];
      }
    } catch (_) {
      return null;
    }
  }

  bool _safeWriteTarget(_StorageTarget target, String key, String value) {
    try {
      switch (target) {
        case _StorageTarget.local:
          return _localTarget.write(key, value);
        case _StorageTarget.session:
          return _sessionTarget.write(key, value);
        case _StorageTarget.memory:
          _memory[key] = value;
          return true;
      }
    } catch (_) {
      return false;
    }
  }

  bool _safeRemoveTarget(_StorageTarget target, String key) {
    try {
      switch (target) {
        case _StorageTarget.local:
          return _localTarget.remove(key);
        case _StorageTarget.session:
          return _sessionTarget.remove(key);
        case _StorageTarget.memory:
          _memory.remove(key);
          return true;
      }
    } catch (_) {
      return false;
    }
  }

  void _safeClearTarget(_StorageTarget target) {
    try {
      switch (target) {
        case _StorageTarget.local:
          _localTarget.clear();
        case _StorageTarget.session:
          _sessionTarget.clear();
        case _StorageTarget.memory:
          _memory.clear();
      }
    } catch (_) {
      // 테스트 정리 단계에서는 clear 예외를 무시합니다.
    }
  }

  String? _readByKey(String key) {
    for (final target in _fallbackTargets()) {
      final value = _safeReadTarget(target, key);
      if (value != null) {
        return value;
      }
    }
    return null;
  }

  void _writeByKey(String key, String value) {
    for (final target in _fallbackTargets()) {
      if (_safeWriteTarget(target, key, value)) {
        return;
      }
    }
  }

  void _removeEverywhere(String key) {
    _safeRemoveTarget(_StorageTarget.local, key);
    _safeRemoveTarget(_StorageTarget.session, key);
    _memory.remove(key);
  }

  @override
  String? read() {
    final current = _readByKey(LocaleStoragePolicy.currentKey);
    if (LocaleStoragePolicy.hasValue(current)) {
      return current;
    }

    final legacy = _readByKey(LocaleStoragePolicy.legacyKey);
    if (LocaleStoragePolicy.shouldMigrateLegacy(
          current: current,
          legacy: legacy,
        ) &&
        legacy != null) {
      _writeByKey(LocaleStoragePolicy.currentKey, legacy);
      _removeEverywhere(LocaleStoragePolicy.legacyKey);
      return legacy;
    }

    return null;
  }

  @override
  void write(String locale) {
    _writeByKey(LocaleStoragePolicy.currentKey, locale);
  }

  @override
  void setTestMode(LocaleStorageTestMode mode) {
    _mode = mode;
  }

  @override
  void clearForTests() {
    _safeClearTarget(_StorageTarget.local);
    _safeClearTarget(_StorageTarget.session);
    _memory.clear();
    _mode = LocaleStorageTestMode.normal;
  }

  @override
  void seedLegacyForTests(String locale) {
    _writeByKey(LocaleStoragePolicy.legacyKey, locale);
  }

  @override
  LocaleStorageDebugState debugStateForTests() {
    return LocaleStorageDebugState(
      mode: _mode,
      localCurrent: _safeReadTarget(
        _StorageTarget.local,
        LocaleStoragePolicy.currentKey,
      ),
      localLegacy: _safeReadTarget(
        _StorageTarget.local,
        LocaleStoragePolicy.legacyKey,
      ),
      sessionCurrent: _safeReadTarget(
        _StorageTarget.session,
        LocaleStoragePolicy.currentKey,
      ),
      sessionLegacy: _safeReadTarget(
        _StorageTarget.session,
        LocaleStoragePolicy.legacyKey,
      ),
      memoryCurrent: _memory[LocaleStoragePolicy.currentKey],
      memoryLegacy: _memory[LocaleStoragePolicy.legacyKey],
    );
  }
}
