import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

import '../game/models/booster.dart';
import 'streak.dart';

class StageProgress {
  const StageProgress({required this.stars, required this.bestScore});

  final int stars;
  final int bestScore;
}

/// Versioned JSON-over-prefs persistence for progress. Total payload stays
/// tiny (<100 KB), so a key-value blob beats a database here.
class SaveRepository {
  SaveRepository(this._prefs) {
    final raw = _prefs.getString(_key);
    if (raw != null) {
      final json = jsonDecode(raw) as Map<String, dynamic>;
      final progress = json['progress'] as Map<String, dynamic>? ?? {};
      for (final entry in progress.entries) {
        final value = entry.value as Map<String, dynamic>;
        _progress[entry.key] = StageProgress(
          stars: value['stars'] as int,
          bestScore: value['bestScore'] as int,
        );
      }
      final streak = json['streak'] as Map<String, dynamic>?;
      if (streak != null) {
        _streak = StreakState(
          current: streak['current'] as int,
          best: streak['best'] as int,
          lastYmd: streak['lastYmd'] as String?,
        );
      }
      _tutorialDone =
          (json['flags'] as Map<String, dynamic>?)?['tutorialDone'] as bool? ??
              false;
      _endlessBest =
          (json['endless'] as Map<String, dynamic>?)?['best'] as int? ?? 0;
      _adsRemoved =
          (json['flags'] as Map<String, dynamic>?)?['adsRemoved'] as bool? ??
              false;
      _soundEnabled =
          (json['flags'] as Map<String, dynamic>?)?['soundEnabled'] as bool? ??
              true;
      _musicEnabled =
          (json['flags'] as Map<String, dynamic>?)?['musicEnabled'] as bool? ??
              true;
      _reviewRequested = (json['flags']
              as Map<String, dynamic>?)?['reviewRequested'] as bool? ??
          false;
      final boosters = json['boosters'] as Map<String, dynamic>? ?? {};
      for (final t in BoosterType.values) {
        _boosters[t] = boosters[t.name] as int? ?? 0;
      }
      final daily = json['daily'] as Map<String, dynamic>?;
      _dailyLastClaimedYmd = daily?['lastYmd'] as String?;
      _dailyCalendarDay = daily?['day'] as int? ?? 0;
    }
  }

  static const _key = 'save_v1';

  static Future<SaveRepository> open() async =>
      SaveRepository(await SharedPreferences.getInstance());

  final SharedPreferences _prefs;
  final Map<String, StageProgress> _progress = {};
  StreakState _streak = StreakState.initial;
  bool _tutorialDone = false;
  int _endlessBest = 0;
  bool _adsRemoved = false;
  bool _soundEnabled = true;
  bool _musicEnabled = true;
  bool _reviewRequested = false;
  final Map<BoosterType, int> _boosters = {
    for (final t in BoosterType.values) t: 0,
  };
  String? _dailyLastClaimedYmd;
  int _dailyCalendarDay = 0;

  StreakState get streak => _streak;
  bool get tutorialDone => _tutorialDone;
  int get endlessBest => _endlessBest;
  bool get adsRemoved => _adsRemoved;
  bool get soundEnabled => _soundEnabled;
  bool get musicEnabled => _musicEnabled;

  /// Whether we've already asked the player for a store review (one-time).
  bool get reviewRequested => _reviewRequested;

  /// Distinct stages the player has cleared (any season, >=1 star). Used to
  /// gate the review prompt behind a player who is clearly invested.
  int get stagesClearedCount =>
      _progress.values.where((p) => p.stars > 0).length;

  Future<void> markTutorialDone() {
    _tutorialDone = true;
    return _flush();
  }

  Future<void> setAdsRemoved(bool value) {
    _adsRemoved = value;
    return _flush();
  }

  Future<void> setSoundEnabled(bool value) {
    _soundEnabled = value;
    return _flush();
  }

  Future<void> setMusicEnabled(bool value) {
    _musicEnabled = value;
    return _flush();
  }

  Future<void> markReviewRequested() {
    _reviewRequested = true;
    return _flush();
  }

  int boosterCount(BoosterType type) => _boosters[type] ?? 0;

  Future<void> grantBooster(BoosterType type, [int n = 1]) {
    _boosters[type] = (_boosters[type] ?? 0) + n;
    return _flush();
  }

  /// Spends one booster. Returns false (and changes nothing) when none are left.
  Future<bool> consumeBooster(BoosterType type) async {
    final have = _boosters[type] ?? 0;
    if (have <= 0) return false;
    _boosters[type] = have - 1;
    await _flush();
    return true;
  }

  String? get dailyLastClaimedYmd => _dailyLastClaimedYmd;
  int get dailyCalendarDay => _dailyCalendarDay;

  Future<void> recordDailyClaim(String ymd, int day) {
    _dailyLastClaimedYmd = ymd;
    _dailyCalendarDay = day;
    return _flush();
  }

  Future<void> recordEndlessScore(int score) {
    if (score > _endlessBest) _endlessBest = score;
    return _flush();
  }

  Future<void> saveStreak(StreakState streak) {
    _streak = streak;
    return _flush();
  }

  static String _id(String seasonId, String stageId) => '$seasonId/$stageId';

  /// Immutable copy of all progress, keyed `seasonId/stageId`.
  Map<String, StageProgress> snapshot() => Map.unmodifiable(_progress);

  StageProgress? progressFor(String seasonId, String stageId) =>
      _progress[_id(seasonId, stageId)];

  Future<void> recordResult({
    required String seasonId,
    required String stageId,
    required int stars,
    required int score,
  }) async {
    final id = _id(seasonId, stageId);
    final old = _progress[id];
    _progress[id] = StageProgress(
      stars: old == null || stars > old.stars ? stars : old.stars,
      bestScore:
          old == null || score > old.bestScore ? score : old.bestScore,
    );
    await _flush();
  }

  int totalStars(String seasonId) {
    var total = 0;
    for (final entry in _progress.entries) {
      if (entry.key.startsWith('$seasonId/')) total += entry.value.stars;
    }
    return total;
  }

  /// Index of the furthest playable stage: the first without a star, or the
  /// last stage when everything is starred.
  int highestUnlockedIndex(String seasonId, List<String> stageIds) {
    for (var i = 0; i < stageIds.length; i++) {
      final progress = progressFor(seasonId, stageIds[i]);
      if (progress == null || progress.stars == 0) return i;
    }
    return stageIds.length - 1;
  }

  Future<void> _flush() => _prefs.setString(
        _key,
        jsonEncode({
          'saveVersion': 1,
          'progress': {
            for (final entry in _progress.entries)
              entry.key: {
                'stars': entry.value.stars,
                'bestScore': entry.value.bestScore,
              },
          },
          'streak': {
            'current': _streak.current,
            'best': _streak.best,
            'lastYmd': _streak.lastYmd,
          },
          'flags': {
            'tutorialDone': _tutorialDone,
            'adsRemoved': _adsRemoved,
            'soundEnabled': _soundEnabled,
            'musicEnabled': _musicEnabled,
            'reviewRequested': _reviewRequested,
          },
          'endless': {'best': _endlessBest},
          'boosters': {for (final t in BoosterType.values) t.name: _boosters[t]},
          'daily': {'lastYmd': _dailyLastClaimedYmd, 'day': _dailyCalendarDay},
        }),
      );
}
