import 'dart:convert';

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:sheetless/core/models/change.dart';
import 'package:sheetless/core/models/config.dart';
import 'package:sheetless/core/models/sheet.dart';

/// Keys for secure storage (credentials and tokens).
enum SecureStorageKey { url, refreshToken, username }

/// Data class for storing annotations with metadata.
class StoredAnnotation {
  final String annotationsJson;
  final DateTime lastModified;

  StoredAnnotation({required this.annotationsJson, required this.lastModified});

  Map<String, dynamic> toMap() => {
    'annotationsJson': annotationsJson,
    'lastModified': lastModified.toIso8601String(),
  };

  factory StoredAnnotation.fromMap(Map<dynamic, dynamic> map) {
    return StoredAnnotation(
      annotationsJson: map['annotationsJson'] as String,
      lastModified: DateTime.parse(map['lastModified'] as String),
    );
  }
}

/// Service for managing local storage operations.
///
/// Uses [FlutterSecureStorage] for sensitive data (credentials, tokens)
/// and [Hive] for general app data (config, sheet access times, change queue,
/// and PDF annotations).
/// Data class for a pending annotation upload.
class PendingAnnotationUpload {
  final String sheetUuid;
  final int page;
  final String annotationsJson;
  final DateTime lastModified;

  PendingAnnotationUpload({
    required this.sheetUuid,
    required this.page,
    required this.annotationsJson,
    required this.lastModified,
  });

  Map<String, dynamic> toMap() => {
    'sheetUuid': sheetUuid,
    'page': page,
    'annotationsJson': annotationsJson,
    'lastModified': lastModified.toIso8601String(),
  };

  factory PendingAnnotationUpload.fromMap(Map<dynamic, dynamic> map) {
    return PendingAnnotationUpload(
      sheetUuid: map['sheetUuid'] as String,
      page: map['page'] as int,
      annotationsJson: map['annotationsJson'] as String,
      lastModified: DateTime.parse(map['lastModified'] as String),
    );
  }

  /// Unique key for deduplication (newer uploads replace older ones).
  String get key => '${sheetUuid}_page_$page';
}

/// Service for managing local storage operations.
///
/// Uses [FlutterSecureStorage] for sensitive data (credentials, tokens)
/// and [Hive] for general app data (config, sheet access times, change queue,
/// and PDF annotations).
class StorageService {
  // Hive box names
  static const String _sheetAccessTimesBox = 'sheetAccessTimes';
  static const String _configBox = 'config';
  static const String _changeQueueBox = 'changeQueue';
  static const String _annotationsBox = 'annotations';
  static const String _sheetsBox = 'sheets';
  static const String _pendingAnnotationsBox = 'pendingAnnotations';

  late final FlutterSecureStorage _secureStorage;

  StorageService() {
    _secureStorage = FlutterSecureStorage();
  }

  // ---------------------------------------------------------------------------
  // Secure Storage (Credentials & Tokens)
  // ---------------------------------------------------------------------------

  /// Reads a value from secure storage.
  Future<String?> readSecure(SecureStorageKey key) {
    return _secureStorage.read(key: key.name);
  }

  /// Writes a value to secure storage.
  ///
  /// Pass `null` to delete the key.
  Future<void> writeSecure(SecureStorageKey key, String? value) {
    return _secureStorage.write(key: key.name, value: value);
  }

  /// Clears all user data except URL and email.
  ///
  /// Called on logout to ensure a clean state for the next user,
  /// while preserving server URL and email for convenience.
  Future<void> clearAllUserData() async {
    // Clear JWT token
    await writeSecure(SecureStorageKey.refreshToken, null);

    // Clear all Hive boxes
    final sheetAccessTimesBox = await Hive.openBox(_sheetAccessTimesBox);
    await sheetAccessTimesBox.clear();

    final configBox = await Hive.openBox(_configBox);
    await configBox.clear();

    final changeQueueBox = await Hive.openBox(_changeQueueBox);
    await changeQueueBox.clear();

    final annotationsBox = await Hive.openBox(_annotationsBox);
    await annotationsBox
        .clear(); // TODO: this can lead to a loss of important data. Make sure the user is warned

    final sheetsBox = await Hive.openBox(_sheetsBox);
    await sheetsBox.clear();

    final pendingAnnotationsBox = await Hive.openBox(_pendingAnnotationsBox);
    await pendingAnnotationsBox.clear();
  }

  // ---------------------------------------------------------------------------
  // Config
  // ---------------------------------------------------------------------------

  /// Reads the app configuration from storage.
  Future<Config> readConfig() async {
    final box = await Hive.openBox(_configBox);
    return Config(
      twoPageMode: box.get(Config.keyTwoPageMode) ?? false,
      fullscreen: box.get(Config.keyFullscreen) ?? false,
    );
  }

  /// Writes the app configuration to storage.
  Future<void> writeConfig(Config config) async {
    final box = await Hive.openBox(_configBox);
    await box.put(Config.keyTwoPageMode, config.twoPageMode);
    await box.put(Config.keyFullscreen, config.fullscreen);
  }

  // ---------------------------------------------------------------------------
  // Sheet Access Times (for sorting by recency)
  // ---------------------------------------------------------------------------

  /// Reads all sheet access times.
  ///
  /// Returns a map of sheet UUID to last access time.
  Future<Map<String, DateTime>> readSheetAccessTimes() async {
    final box = await Hive.openBox(_sheetAccessTimesBox);
    return box.toMap().map(
      (key, value) => MapEntry(key as String, DateTime.parse(value as String)),
    );
  }

  /// Records when a sheet was last accessed.
  Future<void> writeSheetAccessTime(String uuid, DateTime datetime) async {
    final box = await Hive.openBox(_sheetAccessTimesBox);
    await box.put(uuid, datetime.toIso8601String());
  }

  // ---------------------------------------------------------------------------
  // Change Queue (Offline Sync)
  // ---------------------------------------------------------------------------

  /// Adds a change to the offline queue.
  Future<void> writeChange(Change change) async {
    final box = await Hive.openBox(_changeQueueBox);
    await box.add(change.toMap());
  }

  /// Reads all pending changes from the queue.
  Future<ChangeQueue> readChangeQueue() async {
    final box = await Hive.openBox(_changeQueueBox);
    final queue = ChangeQueue();

    for (final map in box.values) {
      queue.addChange(Change.fromMap(map as Map<dynamic, dynamic>));
    }

    return queue;
  }

  /// Removes the oldest change from the queue.
  ///
  /// Call this after successfully syncing a change to the server.
  Future<void> deleteOldestChange() async {
    final box = await Hive.openBox(_changeQueueBox);
    if (box.isNotEmpty) {
      await box.deleteAt(0);
    }
  }

  // ---------------------------------------------------------------------------
  // Annotations (PDF Drawing Persistence)
  // ---------------------------------------------------------------------------

  /// Generates a storage key for a specific page's annotations.
  String _annotationKey(String sheetUuid, int pageNumber) {
    return '${sheetUuid}_page_$pageNumber';
  }

  /// Reads annotations for a specific sheet page.
  ///
  /// Returns the JSON string of annotations, or null if none exist.
  Future<String?> readAnnotations(String sheetUuid, int pageNumber) async {
    final box = await Hive.openBox(_annotationsBox);
    final value = box.get(_annotationKey(sheetUuid, pageNumber));

    // Handle legacy format (plain string) and new format (map with metadata)
    if (value == null) return null;
    if (value is String) return value;
    if (value is Map) {
      final stored = StoredAnnotation.fromMap(value);
      return stored.annotationsJson;
    }
    return null;
  }

  /// Writes annotations with a specific lastModified timestamp.
  ///
  /// Used when syncing from server to preserve server's timestamp.
  Future<void> writeAnnotationsWithMetadata(
    String sheetUuid,
    int pageNumber,
    String? annotationsJson,
    DateTime lastModified,
  ) async {
    final box = await Hive.openBox(_annotationsBox);
    final key = _annotationKey(sheetUuid, pageNumber);

    if (annotationsJson == null || annotationsJson.isEmpty) {
      await box.delete(key);
    } else {
      final stored = StoredAnnotation(
        annotationsJson: annotationsJson,
        lastModified: lastModified,
      );
      await box.put(key, stored.toMap());
    }
  }

  /// Reads all annotations for a sheet (all pages).
  ///
  /// Returns a map of page number to JSON string.
  Future<Map<int, String>> readAllAnnotations(String sheetUuid) async {
    final box = await Hive.openBox(_annotationsBox);
    final prefix = '${sheetUuid}_page_';
    final result = <int, String>{};

    for (final key in box.keys) {
      if (key is String && key.startsWith(prefix)) {
        final pageStr = key.substring(prefix.length);
        final pageNumber = int.tryParse(pageStr);
        if (pageNumber != null) {
          final value = box.get(key);
          if (value != null) {
            // Handle legacy format (plain string) and new format (map)
            if (value is String && value.isNotEmpty) {
              result[pageNumber] = value;
            } else if (value is Map) {
              final stored = StoredAnnotation.fromMap(value);
              if (stored.annotationsJson.isNotEmpty) {
                result[pageNumber] = stored.annotationsJson;
              }
            }
          }
        }
      }
    }

    return result;
  }

  /// Reads all annotations with metadata for a sheet (all pages).
  ///
  /// Returns a map of page number to [StoredAnnotation].
  Future<Map<int, StoredAnnotation>> readAllAnnotationsWithMetadata(
    String sheetUuid,
  ) async {
    final box = await Hive.openBox(_annotationsBox);
    final prefix = '${sheetUuid}_page_';
    final result = <int, StoredAnnotation>{};

    for (final key in box.keys) {
      if (key is String && key.startsWith(prefix)) {
        final pageStr = key.substring(prefix.length);
        final pageNumber = int.tryParse(pageStr);
        if (pageNumber != null) {
          final value = box.get(key);
          if (value != null) {
            StoredAnnotation? stored;
            // Handle legacy format (plain string) and new format (map)
            if (value is String && value.isNotEmpty) {
              stored = StoredAnnotation(
                annotationsJson: value,
                lastModified: DateTime.fromMillisecondsSinceEpoch(0),
              );
            } else if (value is Map) {
              stored = StoredAnnotation.fromMap(value);
            }
            if (stored != null && stored.annotationsJson.isNotEmpty) {
              result[pageNumber] = stored;
            }
          }
        }
      }
    }

    return result;
  }

  /// Deletes all annotations for a sheet.
  Future<void> deleteAllAnnotations(String sheetUuid) async {
    final box = await Hive.openBox(_annotationsBox);
    final prefix = '${sheetUuid}_page_';
    final keysToDelete = box.keys.where(
      (key) => key is String && key.startsWith(prefix),
    );

    for (final key in keysToDelete.toList()) {
      await box.delete(key);
    }
  }

  // ---------------------------------------------------------------------------
  // Sheets Cache (Offline Support)
  // ---------------------------------------------------------------------------

  /// Reads cached sheets from local storage.
  ///
  /// Returns an empty list if no cached sheets exist.
  Future<List<Sheet>> readCachedSheets() async {
    final box = await Hive.openBox(_sheetsBox);
    final sheetsJson = box.get('sheets');

    if (sheetsJson == null) return [];

    final List<dynamic> sheetsList = jsonDecode(sheetsJson as String);
    return sheetsList
        .map((json) => Sheet.fromJson(json as Map<String, dynamic>))
        .toList();
  }

  /// Caches the sheets list to local storage.
  Future<void> writeCachedSheets(List<Sheet> sheets) async {
    final box = await Hive.openBox(_sheetsBox);
    final sheetsJson = jsonEncode(sheets.map((s) => s.toJson()).toList());
    await box.put('sheets', sheetsJson);
  }

  // ---------------------------------------------------------------------------
  // Pending Annotation Uploads (Offline Support)
  // ---------------------------------------------------------------------------

  /// Adds or updates a pending annotation upload.
  ///
  /// If an upload for the same sheet/page already exists, it will be replaced
  /// with the newer version.
  Future<void> writePendingAnnotationUpload(
    PendingAnnotationUpload upload,
  ) async {
    final box = await Hive.openBox(_pendingAnnotationsBox);
    await box.put(upload.key, upload.toMap());
  }

  /// Reads all pending annotation uploads.
  Future<List<PendingAnnotationUpload>> readPendingAnnotationUploads() async {
    final box = await Hive.openBox(_pendingAnnotationsBox);
    final uploads = <PendingAnnotationUpload>[];

    for (final value in box.values) {
      uploads.add(PendingAnnotationUpload.fromMap(value as Map));
    }

    return uploads;
  }

  /// Removes a pending annotation upload after successful sync.
  Future<void> deletePendingAnnotationUpload(String key) async {
    final box = await Hive.openBox(_pendingAnnotationsBox);
    await box.delete(key);
  }

  /// Checks if there are any pending annotation uploads.
  Future<bool> hasPendingAnnotationUploads() async {
    final box = await Hive.openBox(_pendingAnnotationsBox);
    return box.isNotEmpty;
  }

  // ---------------------------------------------------------------------------
  // Change Queue Enhancements
  // ---------------------------------------------------------------------------

  /// Returns the number of pending changes.
  Future<int> getChangeQueueLength() async {
    final box = await Hive.openBox(_changeQueueBox);
    return box.length;
  }

  /// Clears all pending changes.
  ///
  /// Use with caution - only call after all changes are synced.
  Future<void> clearChangeQueue() async {
    final box = await Hive.openBox(_changeQueueBox);
    await box.clear();
  }

  /// Gets all changes as a list (for batch upload).
  Future<List<Change>> readChangeList() async {
    final box = await Hive.openBox(_changeQueueBox);
    return box.values
        .map((map) => Change.fromMap(map as Map<dynamic, dynamic>))
        .toList();
  }
}
