import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/api/api_client.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/api/guarded_api_call.dart';
import '../../auth/data/auth_providers.dart';
import '../domain/admin_ai_categorize_result.dart';
import '../domain/admin_category_node.dart';
import '../domain/admin_pantry_item.dart';
import '../domain/admin_inventory_item.dart';
import '../domain/admin_product.dart';
import '../domain/admin_ai_trace.dart';
import '../domain/admin_ai_trace_detail.dart';
import '../domain/ai_model_info.dart';
import '../domain/pending_product.dart';
import '../domain/receipt_alias.dart';
import '../domain/user_admin.dart';

final adminRepositoryProvider = Provider<AdminRepository>((ref) {
  return AdminRepository(ref.watch(apiClientProvider), ref);
});

class AdminRepository {
  final ApiClient _apiClient;
  final Ref _ref;
  List<AdminProduct>? _selectableProductsCache;
  DateTime? _selectableProductsCacheAt;

  static const Duration _selectableProductsCacheTtl = Duration(seconds: 45);

  AdminRepository(this._apiClient, this._ref);

  void _invalidateSelectableProductsCache() {
    _selectableProductsCache = null;
    _selectableProductsCacheAt = null;
  }

  // ── Interna helpers ────────────────────────────────────────────────────────

  Future<String?> _token() => _ref.read(authStateProvider.future);

  /// GET-anrop som returnerar en typad lista med [fromJson].
  Future<List<T>> _getList<T>(
    String path,
    T Function(Map<String, dynamic>) fromJson, {
    bool requiresAuth = true,
  }) async {
    final token = requiresAuth ? await _token() : null;
    final data = await guardedApiCall(
      _ref,
      () => _apiClient.getJson(path, token: token),
    );
    return _parseList(data, fromJson);
  }

  /// POST-anrop som returnerar ett typat objekt med [fromJson].
  Future<T> _post<T>(
    String path, {
    required Map<String, dynamic>? body,
    required T Function(dynamic) parse,
    bool requiresAuth = true,
  }) async {
    final token = requiresAuth ? await _token() : null;
    final data = await guardedApiCall(
      _ref,
      () => _apiClient.postJson(path, body: body, token: token),
    );
    return parse(data);
  }

  /// PATCH-anrop som returnerar ett typat objekt med [fromJson].
  Future<T> _patch<T>(
    String path, {
    required Map<String, dynamic> body,
    required T Function(Map<String, dynamic>) parse,
  }) async {
    final token = await _token();
    final data = await guardedApiCall(
      _ref,
      () => _apiClient.patchJson(path, body: body, token: token),
    );
    return parse(Map<String, dynamic>.from(data as Map));
  }

  /// Fire-and-forget PATCH.
  Future<void> _patchVoid(String path, Map<String, dynamic> body) async {
    final token = await _token();
    await guardedApiCall(
      _ref,
      () => _apiClient.patchJson(path, body: body, token: token),
    );
  }

  /// Fire-and-forget POST.
  Future<void> _postVoid(String path, [Map<String, dynamic>? body]) async {
    final token = await _token();
    await guardedApiCall(
      _ref,
      () => _apiClient.postJson(path, body: body, token: token),
    );
  }

  /// Fire-and-forget DELETE.
  Future<void> _deleteVoid(String path) async {
    final token = await _token();
    await guardedApiCall(
      _ref,
      () => _apiClient.deleteJson(path, token: token),
    );
  }

  Future<Map<String, dynamic>> _getMap(
    String path, {
    bool requiresAuth = true,
  }) async {
    final token = requiresAuth ? await _token() : null;
    final data = await guardedApiCall(
      _ref,
      () => _apiClient.getJson(path, token: token),
    );
    return Map<String, dynamic>.from(data as Map);
  }

  Future<Map<String, dynamic>> _postMap(
    String path, {
    Map<String, dynamic>? body,
    bool requiresAuth = true,
  }) async {
    final token = requiresAuth ? await _token() : null;
    final data = await guardedApiCall(
      _ref,
      () => _apiClient.postJson(path, body: body, token: token),
    );
    return Map<String, dynamic>.from(data as Map);
  }

  /// Tolerant listparsning — accepterar ren lista eller wrapper ({items, data}).
  static List<T> _parseList<T>(
    dynamic data,
    T Function(Map<String, dynamic>) fromJson,
  ) {
    final List<dynamic> raw;
    if (data is List<dynamic>) {
      raw = data;
    } else if (data is Map<String, dynamic>) {
      raw = (data['items'] as List<dynamic>?) ??
          (data['data'] as List<dynamic>?) ??
          const [];
      if (raw.isEmpty && data.isNotEmpty) {
        debugPrint(
            '[AdminRepository] Unexpected API wrapper shape: ${data.keys}');
      }
    } else {
      raw = const [];
    }
    return raw
        .whereType<Map>()
        .map((e) => fromJson(Map<String, dynamic>.from(e)))
        .toList();
  }

  // ── Användare ──────────────────────────────────────────────────────────────

  Future<List<UserAdmin>> listUsers() =>
      _getList(UserApiPaths.list, UserAdmin.fromJson);

  Future<UserAdmin> setRole(int userId, String newRole) =>
      _patch(UserApiPaths.setRole(userId),
          body: {'role': newRole}, parse: UserAdmin.fromJson);

  Future<UserAdmin> setPremium(int userId, {required bool isPremium}) =>
      _patch(UserApiPaths.setPremium(userId),
          body: {'isPremium': isPremium}, parse: UserAdmin.fromJson);

  Future<UserAdmin> setRecipeSharing(int userId,
          {required bool canShareRecipes}) =>
      _patch(UserApiPaths.setRecipeSharing(userId),
          body: {'canShareRecipes': canShareRecipes},
          parse: UserAdmin.fromJson);

  Future<void> updateEmail(int userId, String email) =>
      _patchVoid(UserApiPaths.updateEmail(userId), {'email': email});

  Future<UserAdmin> createUser({
    required String username,
    required String email,
    required String password,
    String role = 'user',
  }) =>
      _post(
        UserApiPaths.list,
        body: {
          'username': username,
          'email': email,
          'password': password,
          'role': role,
        },
        parse: (d) => UserAdmin.fromJson(d as Map<String, dynamic>),
      );

  Future<void> deleteUser(int userId) =>
      _deleteVoid(UserApiPaths.delete(userId));

  /// Returns `{ temporaryPassword, to, subject, body }`.
  Future<Map<String, dynamic>> resetPassword(int userId) =>
      _postMap(UserApiPaths.resetPassword(userId), body: null);

  // ── Produkter ──────────────────────────────────────────────────────────────

  Future<List<AdminProduct>> listProducts() =>
      _getList(ProductApiPaths.list, AdminProduct.fromJson,
          requiresAuth: false);

  @Deprecated('Use listProducts(). Kept for temporary compatibility.')
  Future<List<AdminProduct>> listGlobalProducts() => listProducts();

  Future<List<PendingProduct>> listPrivateProducts() =>
      _getList(ProductApiPaths.privateList, PendingProduct.fromJson);

  Future<List<AdminProduct>> listSelectableProductsForAdmin({
    bool forceRefresh = false,
  }) async {
    final now = DateTime.now();
    final cached = _selectableProductsCache;
    final cacheAt = _selectableProductsCacheAt;
    if (!forceRefresh && cached != null && cacheAt != null) {
      if (now.difference(cacheAt) <= _selectableProductsCacheTtl) {
        return List<AdminProduct>.from(cached);
      }
    }

    final results = await Future.wait<dynamic>([
      listProducts(),
      listPrivateProducts(),
    ]);
    final globalProducts = results[0] as List<AdminProduct>;
    final privateProducts = results[1] as List<PendingProduct>;

    final merged = <int, AdminProduct>{
      for (final product in globalProducts) product.id: product,
    };

    for (final product in privateProducts) {
      merged[product.id] = AdminProduct(
        id: product.id,
        name: product.name,
        canonicalName: product.canonicalName,
        ownerId: product.ownerId,
        isPrivate: true,
        categoryId: product.categoryId,
        categoryPath: product.categoryPath,
        status: 'private',
      );
    }

    final list = merged.values.toList();
    list.sort(
      (a, b) =>
          a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase()),
    );
    _selectableProductsCache = List<AdminProduct>.from(list);
    _selectableProductsCacheAt = now;
    return list;
  }

  Future<List<AdminProduct>> listDeletedProducts() =>
      _getList(ProductApiPaths.deleted, AdminProduct.fromJson);

  Future<List<PendingProduct>> listPendingProducts() =>
      _getList(ProductApiPaths.pending, PendingProduct.fromJson);

  Future<void> setProductStatus(int productId, String status) =>
      _patchVoid(ProductApiPaths.setStatus(productId), {'status': status})
          .then((_) {
        _invalidateSelectableProductsCache();
      });

  Future<AdminProduct> promotePrivateProduct(int productId) =>
      _post<AdminProduct>(
        ProductApiPaths.promotePrivate(productId),
        body: null,
        parse: (d) =>
            AdminProduct.fromJson(Map<String, dynamic>.from(d as Map)),
      ).then((value) {
        _invalidateSelectableProductsCache();
        return value;
      });

  Future<void> setProductCategory(int productId, {required int? categoryId}) =>
      _patchVoid(ProductApiPaths.update(productId), {'categoryId': categoryId})
          .then((_) {
        _invalidateSelectableProductsCache();
      });

  Future<void> removeProduct(int productId) =>
      _deleteVoid(ProductApiPaths.remove(productId)).then((_) {
        _invalidateSelectableProductsCache();
      });

  Future<void> restoreProduct(int productId) =>
      _postVoid(ProductApiPaths.restore(productId)).then((_) {
        _invalidateSelectableProductsCache();
      });

  // ── Product canonical name updates ────────────────────────────────────────
  Future<void> updateCanonicalName(int productId, String canonicalName) =>
      _patchVoid(
        ProductApiPaths.canonicalName(productId),
        {'canonicalName': canonicalName.trim()},
      ).then((_) {
        _invalidateSelectableProductsCache();
      });

  Future<void> updateCanonicalNamePrivate(
          int productId, String canonicalName) =>
      _patchVoid(
        ProductApiPaths.canonicalNamePrivate(productId),
        {'canonicalName': canonicalName.trim()},
      ).then((_) {
        _invalidateSelectableProductsCache();
      });

  // ── Product merging ────────────────────────────────────────────────────────
  Future<void> mergeProductsPrivate({
    required int sourceProductId,
    required int targetProductId,
  }) =>
      _postVoid(ProductApiPaths.mergePrivate, {
        'sourceProductId': sourceProductId,
        'targetProductId': targetProductId,
      }).then((_) {
        _invalidateSelectableProductsCache();
      });

  /// Skapar en ny aktiv produkt (kräver admin). Returnerar `{id, name, categoryId?}`.
  Future<Map<String, dynamic>> createProduct(String name, {int? categoryId}) =>
      _postMap(
        ProductApiPaths.list,
        body: {
          'name': name.trim(),
          if (categoryId != null) 'categoryId': categoryId,
        },
      ).then((value) {
        _invalidateSelectableProductsCache();
        return value;
      });

  int _parseUpdatedCount(dynamic data) {
    if (data is! Map) {
      debugPrint(
          '[AdminRepository] bulkSetCategory unexpected response type: ${data.runtimeType}');
      return 0;
    }
    final map = Map<String, dynamic>.from(data);
    return (map['updated'] as num?)?.toInt() ?? 0;
  }

  Future<int> bulkSetCategory(List<int> ids, {required int? categoryId}) =>
      _post<int>(
        ProductApiPaths.bulkUpdate,
        body: {'ids': ids, 'categoryId': categoryId},
        parse: _parseUpdatedCount,
      ).then((value) {
        _invalidateSelectableProductsCache();
        return value;
      });

  Future<void> mergeProducts({
    required int sourceProductId,
    required int targetProductId,
  }) =>
      _postVoid(ProductApiPaths.merge, {
        'sourceProductId': sourceProductId,
        'targetProductId': targetProductId,
      }).then((_) {
        _invalidateSelectableProductsCache();
      });

  Future<Map<String, dynamic>> previewMerge({
    required int sourceProductId,
    required int targetProductId,
  }) =>
      _getMap(ProductApiPaths.mergePreview(sourceProductId, targetProductId));

  Future<List<AdminAiCategorizeResult>> aiCategorizeBulk({
    List<int>? productIds,
  }) async {
    final token = await _token();
    final data = await guardedApiCall(
      _ref,
      () => _apiClient.postJson(
        ProductApiPaths.aiCategorizeBulk,
        body: (productIds == null || productIds.isEmpty)
            ? null
            : {'productIds': productIds},
        token: token,
      ),
    );
    return _parseList(data, AdminAiCategorizeResult.fromJson)
        .where((e) => e.productId > 0 && e.categoryId > 0)
        .toList();
  }

  // ── Kategorier ─────────────────────────────────────────────────────────────

  Future<List<AdminCategoryNode>> listCategoryTree() => _getList(
        CategoryApiPaths.tree,
        AdminCategoryNode.fromJson,
        requiresAuth: false,
      );

  // ── AI-modeller ────────────────────────────────────────────────────────────

  /// OBS: endpointen /ai/models kräver autentisering.
  Future<List<AiModelInfo>> listAiModels() =>
      _getList(AiApiPaths.models, AiModelInfo.fromJson);

  Future<AdminAiTraceListResponse> listAiTraces({
    required AdminAiTraceSource source,
    int limit = 25,
    String? cursor,
    String? period,
    bool onlyErrors = false,
  }) =>
      _getMap(
        AiApiPaths.traces(
          source: source.apiValue,
          limit: limit,
          cursor: cursor,
          period: period,
          onlyErrors: onlyErrors,
        ),
      ).then(AdminAiTraceListResponse.fromJson);

  Future<AdminAiTraceDetail> getAiTraceById(String traceId) =>
      _getMap(AiApiPaths.traceById(traceId)).then(AdminAiTraceDetail.fromJson);

  // ── Kvittoalias (admin/global fallback) ───────────────────────────────────

  Future<List<ReceiptAlias>> listReceiptAliases() =>
      _getList(ReceiptAliasApiPaths.list, ReceiptAlias.fromJson);

  Future<void> upsertReceiptAlias({
    required String receiptName,
    required int productId,
    bool isGlobal = false,
  }) =>
      _postVoid(ReceiptAliasApiPaths.list, {
        'receiptName': receiptName,
        'productId': productId,
        'isGlobal': isGlobal,
      });

  Future<void> updateReceiptAlias(
    int id, {
    String? receiptName,
    int? productId,
    bool? isGlobal,
  }) {
    final body = <String, dynamic>{
      if (receiptName != null) 'receiptName': receiptName,
      if (productId != null) 'productId': productId,
      if (isGlobal != null) 'isGlobal': isGlobal,
    };

    return _patchVoid(ReceiptAliasApiPaths.update(id), body);
  }

  Future<void> removeReceiptAlias(int id) =>
      _deleteVoid(ReceiptAliasApiPaths.remove(id));

  // ── Admin inventory (global tabellhantering) ─────────────────────────────

  Future<List<AdminInventoryItem>> listAdminInventory({
    int? userId,
    String? sort,
  }) {
    final path = AdminInventoryApiPaths.withFilters(userId: userId, sort: sort);
    return _getList(path, AdminInventoryItem.fromJson);
  }

  Future<AdminInventoryItem> createAdminInventory({
    int? userId,
    required int productId,
    required double quantity,
    required String unit,
    String? location,
    String? brand,
    String? receiptName,
    String? suitableFor,
    String? comment,
  }) {
    return _post<AdminInventoryItem>(
      AdminInventoryApiPaths.list,
      body: {
        if (userId != null) 'userId': userId,
        'productId': productId,
        'quantity': quantity,
        'unit': unit,
        if (location != null && location.trim().isNotEmpty)
          'location': location.trim(),
        if (brand != null && brand.trim().isNotEmpty) 'brand': brand.trim(),
        if (receiptName != null && receiptName.trim().isNotEmpty)
          'receiptName': receiptName.trim(),
        if (suitableFor != null && suitableFor.trim().isNotEmpty)
          'suitableFor': suitableFor.trim(),
        if (comment != null && comment.trim().isNotEmpty)
          'comment': comment.trim(),
      },
      parse: (d) =>
          AdminInventoryItem.fromJson(Map<String, dynamic>.from(d as Map)),
    );
  }

  Future<AdminInventoryItem> updateAdminInventory(
    int inventoryId, {
    int? productId,
    double? quantity,
    String? unit,
    String? location,
    String? brand,
    String? receiptName,
    String? suitableFor,
    String? comment,
  }) {
    final body = <String, dynamic>{
      if (productId != null) 'productId': productId,
      if (quantity != null) 'quantity': quantity,
      if (unit != null) 'unit': unit,
      if (location != null) 'location': location,
      if (brand != null) 'brand': brand,
      if (receiptName != null) 'receiptName': receiptName,
      if (suitableFor != null) 'suitableFor': suitableFor,
      if (comment != null) 'comment': comment,
    };

    return _patch(
      AdminInventoryApiPaths.update(inventoryId),
      body: body,
      parse: AdminInventoryItem.fromJson,
    );
  }

  Future<void> removeAdminInventory(int inventoryId) =>
      _deleteVoid(AdminInventoryApiPaths.remove(inventoryId));

  Future<void> moveAdminInventoryToPantry(int inventoryId) =>
      _postVoid(AdminInventoryApiPaths.moveToPantry(inventoryId));

  // ── Admin pantry ──────────────────────────────────────────────────────────

  Future<List<AdminPantryItem>> listAdminPantry({int? userId}) {
    final params = <String, String>{};
    if (userId != null) params['userId'] = '$userId';
    final path = params.isEmpty
        ? PantryApiPaths.adminList
        : '${PantryApiPaths.adminList}?${params.entries.map((e) => '${Uri.encodeQueryComponent(e.key)}=${Uri.encodeQueryComponent(e.value)}').join('&')}';
    return _getList(path, AdminPantryItem.fromJson);
  }

  Future<void> removeAdminPantryItem(int pantryItemId) =>
      _deleteVoid(PantryApiPaths.adminRemove(pantryItemId));

  Future<AdminPantryItem> createAdminPantry({
    int? userId,
    required int productId,
    String? location,
  }) {
    return _post<AdminPantryItem>(
      PantryApiPaths.adminCreate,
      body: {
        if (userId != null) 'userId': userId,
        'productId': productId,
        if (location != null && location.trim().isNotEmpty)
          'location': location.trim(),
      },
      parse: (d) =>
          AdminPantryItem.fromJson(Map<String, dynamic>.from(d as Map)),
    );
  }

  Future<AdminPantryItem> updateAdminPantry(
    int pantryItemId, {
    int? productId,
    String? location,
  }) {
    return _patch(
      PantryApiPaths.adminUpdate(pantryItemId),
      body: {
        if (productId != null) 'productId': productId,
        if (location != null) 'location': location,
      },
      parse: AdminPantryItem.fromJson,
    );
  }

  Future<void> moveAdminPantryToInventory(
    int pantryItemId,
    Map<String, dynamic> body,
  ) =>
      _postVoid(PantryApiPaths.moveToInventoryAdmin(pantryItemId), body);

  Future<void> mergeAdminInventory({
    required int sourceInventoryId,
    required int targetInventoryId,
  }) =>
      _postVoid(AdminInventoryApiPaths.merge, {
        'sourceInventoryId': sourceInventoryId,
        'targetInventoryId': targetInventoryId,
      });

  Future<Map<String, dynamic>> previewAdminInventoryMerge({
    required int sourceInventoryId,
    required int targetInventoryId,
  }) =>
      _getMap(
        AdminInventoryApiPaths.mergePreview(
            sourceInventoryId, targetInventoryId),
      );
}
