import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/enums_and_consts/consts.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/features/attachments/models/attachment_model.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/operation_model.dart';

class OperationsRepository {
  final _supabase = Supabase.instance.client;
  final companyId = GetIt.I.get<SessionCubit>().state.company!.id;

  // --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO ---
  Future<OperationModel> fetchOperationById(String id) async {
    try {
      final response = await _supabase
          .from(Tables.operations)
          .select('''
            *,
            ${Tables.customers}(*), 
            ${Tables.stores}(name),
            ${Tables.staffMembers}(name),
            ${Tables.providers}(name),
            ${Tables.models}(name_with_brand),
            ${Tables.attachments}(*)
          ''')
          .eq('id', id)
          .single();

      return OperationModel.fromMap(response);
    } catch (e) {
      throw Exception('Errore nel caricamento del servizio: $e');
    }
  }

  // 🥷 2. RECUPERO PAGINATO ASSOLUTO CON CONTEGGIO TOTALI
  Future<PaginatedOperations> fetchPaginatedOperations({
    required String companyId,
    String? storeId,
    String? staffId,
    String? providerId,
    required int page, // Usiamo 'page' (1, 2, 3...) invece di 'offset'
    int itemsPerPage = 25, // Default a 25 elementi per pagina
    String? searchTerm,
    DateTimeRange? dateRange,
  }) async {
    try {
      // Calcoliamo il range di partenza e fine per Supabase
      // Es. Pagina 1, 25 items -> range(0, 24)
      // Es. Pagina 2, 25 items -> range(25, 49)
      final from = (page - 1) * itemsPerPage;
      final to = from + itemsPerPage - 1;

      var query = _supabase
          .from(Tables.operations)
          .select('''
            *,
            ${Tables.customers}(*), 
            ${Tables.stores}(name),
            ${Tables.providers}(name, color_hex),
            ${Tables.models}(name_with_brand),
            ${Tables.staffMembers}(name),
            ${Tables.attachments}(*)
          ''')
          .eq('company_id', companyId);

      // Filtro Range Date
      if (dateRange != null) {
        query = query
            .gte('created_at', dateRange.start.toIso8601String())
            .lte('created_at', dateRange.end.toIso8601String());
      }

      if (storeId != null) {
        query = query.or('store_id.eq.$storeId,store_id.is.null');
      }

      if (staffId != null) {
        query = query.or('staff_id.eq.$staffId,staff_id.is.null');
      }

      if (providerId != null) {
        query = query.or('provider_id.eq.$providerId,provider_id.is.null');
      }

      if (searchTerm != null && searchTerm.isNotEmpty) {
        query = query.or(
          'reference.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.name.ilike.%$searchTerm%',
        );
      }

      final response = await query
          .order('created_at', ascending: false)
          .range(from, to)
          .count(CountOption.exact);
      // 3. Estrazione dei dati
      final List<OperationModel> operations = (response.data as List)
          .map((map) => OperationModel.fromMap(map))
          .toList();

      final int totalCount = response.count;

      return PaginatedOperations(
        operations: operations,
        totalCount: totalCount,
      );
    } catch (e) {
      throw Exception('Errore nel recupero della pagina $page: $e');
    }
  }

  // --- RECUPERO PAGINATO CON FILTRI E JOIN ---
  Future<List<OperationModel>> fetchOperations({
    required String companyId,
    String? storeId,
    String? staffId,
    String? providerId,
    required int offset,
    int limit = 50,
    String? searchTerm,
    DateTimeRange? dateRange,
  }) async {
    try {
      var query = _supabase
          .from(Tables.operations)
          .select('''
            *,
            ${Tables.customers}(*), 
            ${Tables.stores}(name),
            ${Tables.providers}(name),
            ${Tables.models}(name_with_brand),
            ${Tables.staffMembers}(name),
            ${Tables.attachments}(*)
          ''')
          .eq('company_id', companyId);

      // Filtro Range Date
      if (dateRange != null) {
        query = query
            .gte('created_at', dateRange.start.toIso8601String())
            .lte('created_at', dateRange.end.toIso8601String());
      }

      if (storeId != null) {
        query = query.or('store_id.eq.$storeId,store_id.is.null');
      }

      if (staffId != null) {
        query = query.or('staff_id.eq.$staffId,staff_id.is.null');
      }

      if (providerId != null) {
        query = query.or('provider_id.eq.$providerId,provider_id.is.null');
      }

      if (searchTerm != null && searchTerm.isNotEmpty) {
        // Filtra sui campi della tabella principale O su quelli della tabella joinata
        query = query.or(
          'reference.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.name.ilike.%$searchTerm%',
        );
      }

      final response = await query
          .order('created_at', ascending: false)
          .range(offset, offset + limit - 1);

      return (response as List)
          .map((map) => OperationModel.fromMap(map))
          .toList();
    } catch (e) {
      throw Exception('$e');
    }
  }

  Stream<List<OperationModel>> watchStoreOperations({
    required String storeId,
    required int limit,
  }) {
    return _supabase
        .from(Tables.operations)
        .stream(primaryKey: ['id'])
        .eq('store_id', storeId)
        .order('created_at', ascending: false)
        .limit(limit)
        .map(
          (listOfMaps) =>
              listOfMaps.map((map) => OperationModel.fromMap(map)).toList(),
        );
  }

  // --- SALVATAGGIO COMPLETO (PRIMA PADRE, POI FIGLI) ---
  Future<OperationModel> saveFullOperation({
    required OperationModel operation,
  }) async {
    try {
      // 1. Salvataggio classico dell'operazione corrente
      final response = await _supabase
          .from(Tables.operations)
          .upsert(operation.toMap())
          .select(
            '*, ${Tables.providers}(*), ${Tables.models}(*), ${Tables.stores}(*), ${Tables.staffMembers}(*), ${Tables.customers}(*), ${Tables.attachments}(*)',
          )
          .single();

      final savedOperation = OperationModel.fromMap(response);

      // 2. ALLINEAMENTO BATCH SEMPRE ATTIVO!
      if (operation.batchUuid.isNotEmpty) {
        await _supabase
            .from(Tables.operations)
            .update({'note': operation.note}) // Spalmiamo la nota attuale
            .eq(
              'batch_uuid',
              operation.batchUuid,
            ); // Su tutte le pratiche di questo scontrino
      }

      return savedOperation;
    } catch (e) {
      throw Exception("Errore nel salvataggio dell'operazione: $e");
    }
  }

  // --- ELIMINAZIONE ---
  Future<void> deleteOperation(String id) async {
    try {
      await _supabase.from(Tables.operations).delete().eq('id', id);
    } catch (e) {
      throw Exception('$e');
    }
  }

  // --- RECUPERO TIPI CONTENUTI PIÙ FREQUENTI PER AUTOCOMPLETE ---
  Future<List<String>> fetchTopEntertainmentTypes(String companyId) async {
    try {
      // Cerchiamo i tipi più frequenti associati ai servizi di questa company
      // Nota: dobbiamo passare attraverso la tabella 'operation' per filtrare per company_id
      final response = await _supabase
          .from(Tables.operations)
          .select('description')
          .eq('company_id', companyId)
          .eq('type', 'Entertainment')
          .limit(50); // Prendiamo un campione

      // Logica rapida per contare le occorrenze e prendere i primi 5
      final Map<String, int> counts = {};
      for (var item in (response as List)) {
        final description = item['description'] as String;
        counts[description] = (counts[description] ?? 0) + 1;
      }

      var sortedKeys = counts.keys.toList()
        ..sort((a, b) => counts[b]!.compareTo(counts[a]!));

      return sortedKeys.take(5).toList();
    } catch (e) {
      return [
        "Netflix",
        "DAZN",
        "Disney+",
        "Sky",
      ]; // Fallback se non c'è ancora storia
    }
  }

  /// Ascolta in tempo reale i file caricati per una pratica
  Stream<List<AttachmentModel>> getOperationFilesStream(String operationId) {
    return _supabase
        .from(Tables.attachments)
        .stream(primaryKey: ['id'])
        .eq('operation_id', operationId)
        .order('created_at', ascending: false)
        .map(
          (listOfMaps) =>
              listOfMaps.map((map) => AttachmentModel.fromMap(map)).toList(),
        );
  }

  Future<AttachmentModel> uploadAndRegisterOperationFile({
    required String operationId,
    required PlatformFile pickedFile,
  }) async {
    final cleanFileName = pickedFile.name.replaceAll(
      RegExp(r'[^a-zA-Z0-9\.\-]'),
      '_',
    );
    final storagePath =
        '$companyId/operations/$operationId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
    final int fileSize = pickedFile.size;
    final fileToSave = AttachmentModel(
      companyId: GetIt.I.get<SessionCubit>().state.company!.id!,
      operationId: operationId,
      name: cleanFileName.fileNameWithoutExtension(),
      extension: cleanFileName.fileExtension(),
      storagePath: storagePath,
      fileSize: fileSize,
    );
    final String mimeType = fileToSave.extension.toLowerCase() == 'pdf'
        ? 'application/pdf'
        : 'image/${fileToSave.extension}';
    try {
      // Usiamo bytes invece del path per massima compatibilità
      if (pickedFile.bytes == null && pickedFile.path == null) {
        throw 'Impossibile leggere il contenuto del file';
      }

      // Se siamo su desktop/mobile abbiamo il path, su web abbiamo i bytes
      if (pickedFile.bytes != null) {
        await _supabase.storage
            .from('documents')
            .uploadBinary(
              storagePath,
              pickedFile.bytes!,
              fileOptions: FileOptions(contentType: mimeType, upsert: true),
            );
      }

      final response = await _supabase
          .from(Tables.attachments)
          .insert(fileToSave.toMap())
          .select()
          .single();

      return AttachmentModel.fromMap(response);
    } catch (e) {
      throw 'Errore durante l\'upload: $e';
    }
  }

  Future<void> copyFileToCustomer({
    required AttachmentModel file,
    required String customerId,
  }) async {
    await _supabase
        .from(Tables.attachments)
        .update({'customer_id': customerId})
        .eq('id', file.id!);
  }

  Future<void> renameAttachment(String id, String newName) async {
    try {
      await _supabase
          .from(Tables.attachments)
          .update({'name': newName})
          .eq('id', id);
    } catch (e) {
      throw '$e';
    }
  }

  Future<void> deleteSpecificOperationFile(AttachmentModel file) async {
    try {
      if (file.customerId == null) {
        await _supabase.from(Tables.attachments).delete().eq('id', file.id!);
        await _supabase.storage.from('documents').remove([file.storagePath!]);
      } else {
        await _supabase
            .from(Tables.attachments)
            .update({'operation_id': null})
            .eq('id', file.id!);
      }
    } catch (e) {
      throw '$e';
    }
  }

  Future<void> deleteOperationFiles(List<AttachmentModel> files) async {
    if (files.isEmpty) return;
    // 1. Prepariamo le liste di ID e di Percorsi
    final List<String> idsToDelete = [];
    final List<String> idsToEdit = [];
    final List<String> storagePathsToDelete = [];
    for (var file in files) {
      if (file.customerId == null) {
        idsToDelete.add(file.id!);
        storagePathsToDelete.add(file.storagePath!);
      } else {
        idsToEdit.add(file.id!);
      }
    }
    try {
      if (idsToDelete.isNotEmpty) {
        await _supabase
            .from(Tables.attachments)
            .delete()
            .inFilter('id', idsToDelete);
        await _supabase.storage.from('documents').remove(storagePathsToDelete);
      }
      if (idsToEdit.isNotEmpty) {
        await _supabase
            .from(Tables.attachments)
            .update({'operation_id': null})
            .inFilter('id', idsToEdit);
      }
    } on PostgrestException catch (e) {
      throw 'Errore database: ${e.message}';
    } catch (e) {
      throw 'Errore durante l\'eliminazione dei file: $e';
    }
  }
}

class PaginatedOperations {
  final List<OperationModel> operations;
  final int totalCount;

  PaginatedOperations({required this.operations, required this.totalCount});
}
