import 'package:collection/collection.dart';

import '../../widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import '../data/api_response.dart';
import '../models/models.dart';
import '../services/services.dart';
import '../views/corte_caja/corte_caja_ticket.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

class CorteCajaViewModel extends ChangeNotifier {
  List<CorteCaja> _cortes = [];
  List<CorteCaja> get cortes => _cortes;

  bool _isLoading = false;
  bool get isLoading => _isLoading;
  CorteCaja? _selectedCorte;

  double corteFinal = 0.0;
  double totalDepositos = 0.0;
  double totalRetiros = 0.0;
  double totalGastos = 0.0;
  List<Deposito> depositos = [];
  List<Retiro> retiros = [];
  List<Gasto> gastos = [];
  double _ventaEfe = 0.0;
  double _ventaTarj = 0.0;
  double _ventaTransf = 0.0;
  double _totalVenta = 0.0;

  CorteCaja? get selectedCorte => _selectedCorte;

  int _currentPage = 1;
  int _totalCortes = 0;
  int _limit = 10;

  int get currentPage => _currentPage;
  int get totalPedidos => _totalCortes;
  int get totalPages => (_totalCortes / _limit).ceil();

  double get ventaEfe => _ventaEfe;
  double get ventaTarj => _ventaTarj;
  double get ventaTransf => _ventaTransf;
  double get totalVenta => _totalVenta;
  double get salidasTotales => totalRetiros + totalGastos;

  final TextEditingController _ventaEfeController = TextEditingController();
  final TextEditingController _ventaTarjController = TextEditingController();
  final TextEditingController _ventaTransfController = TextEditingController();
  final TextEditingController _totalVentaController = TextEditingController();
  final TextEditingController _fondoController = TextEditingController();
  final TextEditingController _fondoDiaSigController = TextEditingController();
  final TextEditingController _ventaPuntosController = TextEditingController();

  TextEditingController get ventaEfeController => _ventaEfeController;
  TextEditingController get ventaTarjController => _ventaTarjController;
  TextEditingController get ventaTransfController => _ventaTransfController;
  TextEditingController get totalVentaController => _totalVentaController;
  TextEditingController get fondoController => _fondoController;
  TextEditingController get fondoDiaSigController => _fondoDiaSigController;
  TextEditingController get ventaPuntosController => ventaPuntosController;

  List<Pedido> _pedidosConPropinas = [];
  double _totalPropinas = 0.0;

  List<Pedido> get pedidosConPropinas => _pedidosConPropinas;
  double get totalPropinas => _totalPropinas;

  void setIsLoading(bool loading) {
    _isLoading = loading;
    notifyListeners();
  }

  void nextPage() {
    if (_currentPage < totalPages) {
      fetchCortes(page: _currentPage + 1);
    }
  }

  void previousPage() {
    if (_currentPage > 1) {
      fetchCortes(page: _currentPage - 1);
    }
  }

  bool isCorteAbierto() {
    return _selectedCorte != null && _selectedCorte!.fechaCorte == null;
  }

  void actualizarVentaEfectivo(String value) {
    _ventaEfe = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
    _ventaEfeController.text = formatoMiles(_ventaEfe);
    _calcularTotalVenta();
    notifyListeners();
  }

  void actualizarVentaTarjeta(String value) {
    _ventaTarj = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
    _ventaTarjController.text = formatoMiles(_ventaTarj);
    _calcularTotalVenta();
    notifyListeners();
  }

  void actualizarVentaTransferencia(String value) {
    _ventaTransf = double.tryParse(value.replaceAll(',', '')) ?? 0.0;
    _ventaTransfController.text = formatoMiles(_ventaTransf);
    _calcularTotalVenta();
    notifyListeners();
  }

  void _calcularTotalVenta() {
    _totalVenta = _ventaEfe + _ventaTarj + _ventaTransf;
    _totalVentaController.text = formatoMiles(_totalVenta);
  }

  Future<String?> createCorteCaja() async {
    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();

    double? fondoDiaSigAnterior =
        await repoCorte.obtenerFondoDiaSigDelUltimoCorte();

    _selectedCorte = CorteCaja(
      id: Uuid().v4(),
      fechaApertura: DateTime.now().toUtc(),
      creado: DateTime.now().toUtc(),
      fondo: fondoDiaSigAnterior ?? 0.0,
    );

    await _selectedCorte!.guardar();
    notifyListeners();
    return _selectedCorte!.id;
  }

  Future<void> eliminarDeposito(String id) async {
    final deposito = depositos.firstWhere((d) => d.id == id);
    deposito.eliminado = DateTime.now().toUtc();
    await deposito.guardar();
    calcularTotalDeposito();
    notifyListeners();
  }

  Future<void> eliminarRetiro(String id) async {
    final retiro = retiros.firstWhere((d) => d.id == id);
    retiro.eliminado = DateTime.now().toUtc();
    await retiro.guardar();
    calcularTotalRetiro();
    notifyListeners();
  }

  Future<void> eliminarGasto(String id) async {
    final gasto = gastos.firstWhere((d) => d.id == id);
    gasto.eliminado = DateTime.now().toUtc();
    await gasto.guardar();
    calcularTotalGasto();
    notifyListeners();
  }

  Future<void> fetchCortes({int page = 1}) async {
    setIsLoading(true);
    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
    _currentPage = page;
    _cortes = await repoCorte.obtenerTodos(orderBy: 'fechaApertura DESC');
    setIsLoading(false);
    notifyListeners();
  }

  Future<void> fetchDepositosAndRetiros(String corteCajaId) async {
    RepoService<Deposito> depositoRepo = RepoService<Deposito>();
    RepoService<Retiro> retiroRepo = RepoService<Retiro>();
    RepoService<Gasto> gastoRepo = RepoService<Gasto>();
    depositos = await depositoRepo.obtenerDepositosPorIdCorteCaja(corteCajaId);
    retiros = await retiroRepo.obtenerRetirosPorIdCorteCaja(corteCajaId);
    gastos = await gastoRepo.obtenerGastosPorIdCorteCaja(corteCajaId);

    print('Depósitos obtenidos: ${depositos.length}');
    print('Retiros obtenidos: ${retiros.length}');
    print('Gastos obtenidos: ${gastos.length}');

    calcularTotalDeposito();
    calcularTotalRetiro();
    calcularTotalGasto();
    notifyListeners();
  }

  Future<CorteCaja?> fetchCorteCajaConDetalles(String idCorteCaja) async {
    // Similar a fetchDepositosAndRetiros, pero devuelve el corte ya cargado
    await fetchDepositosAndRetiros(idCorteCaja);
    return _cortes.firstWhereOrNull((c) => c.id == idCorteCaja);
  }

  void selectCorte(CorteCaja corte) {
    _selectedCorte = corte;
    notifyListeners();
  }

  Future<List<CorteCaja>>
      fetchAllLocalCortesCajasNoSincronizadosOrdenadosPorFecha() async {
    setIsLoading(true);
    var db = await RepoService().db;

    List<Map<String, dynamic>> result = await db!.query('CorteCaja',
        where: 'sincronizado IS NULL', orderBy: 'fechaApertura ASC');

    List<CorteCaja> cortesNoSync =
        result.map((map) => CorteCaja.fromJson(map)).toList();
    setIsLoading(false);
    return cortesNoSync;
  }

  Future<bool> sincronizarCorteCajas() async {
    List<CorteCaja> cortesNoSincronizados =
        await fetchAllLocalCortesCajasNoSincronizadosOrdenadosPorFecha();

    if (cortesNoSincronizados.isNotEmpty) {
      CorteCaja corteNoSincronizado = cortesNoSincronizados.first;

      if (corteNoSincronizado.id != null &&
          corteNoSincronizado.id!.isNotEmpty) {
        await fetchCorteCajaConDetalles(corteNoSincronizado.id!);
        corteNoSincronizado =
            _cortes.firstWhere((c) => c.id == corteNoSincronizado.id);

        corteNoSincronizado.depositos = this.depositos;
        corteNoSincronizado.retiros = this.retiros;
        corteNoSincronizado.gastos = this.gastos;
      }

      Map<String, dynamic> corteJson =
          await prepararCorteCajaParaApi(corteNoSincronizado);

      print('JSON Corte Caja enviado: $corteJson');

      var response = ApiResponse(await BaseService()
          .post('/pos/corte-caja/sincronizar2', body: corteJson));

      if (response.isOk && response.detalle != null) {
        String idWeb = response.detalle!['idWeb'];
        String sincronizado = response.detalle!['sincronizado'];

        await actualizarCorteCajaSincronizado(
            corteNoSincronizado.id!, idWeb, sincronizado);

        return true;
      } else {
        print(
            'Error en la sincronización del corte de caja: ${response.mensaje}');
        return true;
      }
    } else {
      print('No se encontraron cortes de caja no sincronizados.');
      return false;
    }
  }

  Future<void> actualizarCorteCajaSincronizado(
      String idCorteCaja, String idWeb, String sincronizado) async {
    var db = await RepoService().db;

    await db!.update(
      'CorteCaja',
      {
        'idWeb': idWeb,
        'sincronizado': sincronizado,
      },
      where: 'id = ?',
      whereArgs: [idCorteCaja],
    );
  }

  Future<Map<String, dynamic>> prepararCorteCajaParaApi(CorteCaja corte) async {
    String? claveSucursal =
        await RepoService().obtenerClaveSucursalSeleccionada();
    Map<String, dynamic> apiMap = corte.toApi();
    apiMap['claveSucursal'] = claveSucursal;
    if (corte.idWeb != null && corte.idWeb!.isNotEmpty) {
      apiMap['idWeb'] = corte.idWeb;
    }
    return apiMap;
  }

  Future<void> addDeposito(double monto, String descripcion, String? persona,
      String corteCajaId) async {
    Deposito nuevoDeposito = Deposito(
      id: Uuid().v4(),
      idCorteCaja: corteCajaId,
      fechaDeposito: DateTime.now().toUtc(),
      creado: DateTime.now().toUtc(),
      monto: monto,
      descripcion: descripcion,
      persona: persona,
    );
    await nuevoDeposito.guardar();
    depositos.add(nuevoDeposito);
    calcularTotalDeposito();

    calcularCorteFinal();

    if (_selectedCorte != null) {
      _selectedCorte!.sincronizado = null;
      await _selectedCorte!.guardar();
    }

    notifyListeners();
  }

  Future<void> addRetiro(double monto, String descripcion, String? persona,
      String corteCajaId) async {
    Retiro nuevoRetiro = Retiro(
      id: Uuid().v4(),
      idCorteCaja: corteCajaId,
      fechaRetiro: DateTime.now().toUtc(),
      creado: DateTime.now().toUtc(),
      monto: monto,
      descripcion: descripcion,
      persona: persona,
    );
    await nuevoRetiro.guardar();
    retiros.add(nuevoRetiro);
    calcularTotalRetiro();

    calcularCorteFinal();

    if (_selectedCorte != null) {
      _selectedCorte!.sincronizado = null;
      await _selectedCorte!.guardar();
    }

    notifyListeners();
  }

  Future<void> addGasto(double monto, String descripcion, String? persona,
      String corteCajaId) async {
    Gasto nuevoGasto = Gasto(
      id: Uuid().v4(),
      idCorteCaja: corteCajaId,
      fechaGasto: DateTime.now().toUtc(),
      creado: DateTime.now().toUtc(),
      monto: monto,
      descripcion: descripcion,
      persona: persona,
    );
    await nuevoGasto.guardar();
    gastos.add(nuevoGasto);
    calcularTotalGasto();

    calcularCorteFinal();

    if (_selectedCorte != null) {
      _selectedCorte!.sincronizado = null;
      await _selectedCorte!.guardar();
    }

    notifyListeners();
  }

  Future<void> cargarVentasPorCorteId(String idCorteCaja) async {
    setIsLoading(true);
    try {
      RepoService<Pedido> repoPedido = RepoService<Pedido>();
      List<Pedido> pedidos =
          await repoPedido.obtenerPedidosPorCorteCaja(idCorteCaja);

      pedidos = pedidos.where((p) => p.estatus == 'TERMINADO').toList();

      double totalEfectivo = 0;
      double totalTarjeta = 0;
      double totalTransferencia = 0;
      double totalSinCambio = 0;

      for (var pedido in pedidos) {
        totalEfectivo += pedido.cantEfectivo ?? 0;
        totalTarjeta += pedido.cantTarjeta ?? 0;
        totalTransferencia += pedido.cantTransferencia ?? 0;
        totalSinCambio += pedido.totalPedido ?? 0;
      }

      double totalDia = totalEfectivo + totalTarjeta + totalTransferencia;
      double cambio = totalDia - totalSinCambio;
      double efectivoSinCambio = totalEfectivo - cambio;

      _ventaEfeController.text = formatoMiles(efectivoSinCambio);
      _ventaTarjController.text = formatoMiles(totalTarjeta);
      _ventaTransfController.text = formatoMiles(totalTransferencia);
      _totalVentaController.text =
          formatoMiles(efectivoSinCambio + totalTarjeta + totalTransferencia);
    } catch (e) {
      print("Error al cargar pedidos por idCorteCaja: $e");
    } finally {
      setIsLoading(false);
      notifyListeners();
    }
  }

  bool hasOpenCorteCaja() {
    return _cortes.any((corte) => corte.fechaCorte == null);
  }

  Future<void> guardarCorte({bool esCorte = false}) async {
    double fondo =
        double.tryParse(_fondoController.text.replaceAll(',', '')) ?? 0;
    double fondoDiaSig =
        double.tryParse(_fondoDiaSigController.text.replaceAll(',', '')) ?? 0;
    double ventaPuntos =
        double.tryParse(_ventaPuntosController.text.replaceAll(',', '')) ?? 0;
    double ventaEfe =
        double.tryParse(_ventaEfeController.text.replaceAll(',', '')) ?? 0;
    double ventaTrans =
        double.tryParse(_ventaTransfController.text.replaceAll(',', '')) ?? 0;
    double ventaTarj =
        double.tryParse(_ventaTarjController.text.replaceAll(',', '')) ?? 0;
    double gasto = totalGastos;
    double retiro = totalRetiros;
    double deposito = totalDepositos;

    _selectedCorte?.fondo = fondo;
    _selectedCorte?.fondoDiaSig = fondoDiaSig;
    _selectedCorte?.ventaPuntos = ventaPuntos;
    _selectedCorte?.ventaEfe = ventaEfe;
    _selectedCorte?.ventaTrans = ventaTrans;
    _selectedCorte?.ventaTarj = ventaTarj;
    _selectedCorte?.gasto = gasto;
    _selectedCorte?.retiro = retiro;
    _selectedCorte?.deposito = deposito;
    _selectedCorte!.sincronizado = null;

    calcularCorteFinal();

    _selectedCorte?.corteFinal = corteFinal;
    _selectedCorte?.modificado = DateTime.now().toUtc();

    if (esCorte) {
      _selectedCorte?.fechaCorte = DateTime.now().toUtc();
    }

    await _selectedCorte?.guardar();
    notifyListeners();
  }

  void calcularTotalDeposito() {
    totalDepositos = depositos
        .where((deposito) => deposito.eliminado == null)
        .fold(0, (sum, item) => sum + (item.monto ?? 0));
  }

  void calcularTotalRetiro() {
    totalRetiros = retiros
        .where((retiro) => retiro.eliminado == null)
        .fold(0, (sum, item) => sum + (item.monto ?? 0));
  }

  void calcularTotalGasto() {
    totalGastos = gastos
        .where((gasto) => gasto.eliminado == null)
        .fold(0, (sum, item) => sum + (item.monto ?? 0));
  }

  Future<void> buscarPorFecha(DateTime startDate, DateTime endDate) async {
    setIsLoading(true);
    RepoService<CorteCaja> repoCorte = RepoService<CorteCaja>();
    _cortes = await repoCorte.buscarPorFechaCorte(startDate, endDate);
    setIsLoading(false);
    notifyListeners();
  }

  void calcularCorteFinal() {
    final valores = obtenerValoresFondo();
    corteFinal = (valores['fondo']! + totalDepositos + valores['ventaEfe']!) -
        (valores['fondoDiaSig']! + totalRetiros + totalGastos);
    notifyListeners();
  }

  Future<void> imprimirCorteCajaTicket({String? corteCajaId}) async {
    if (corteCajaId != null) {
      await fetchDepositosAndRetiros(corteCajaId);
      _selectedCorte = _cortes.firstWhere((corte) => corte.id == corteCajaId);
      await cargarVentasPorCorteId(corteCajaId);
    }

    if (_selectedCorte == null) {
      print("CorteCaja no encontrado para impresión.");
      return;
    }

    double fondo = _selectedCorte!.fondo ?? 0;
    double fondoDiaSig = _selectedCorte!.fondoDiaSig ?? 0;
    double? ventaEfe = _selectedCorte!.ventaEfe;
    double? ventaTarj = _selectedCorte!.ventaTarj;
    double? ventaTransf = _selectedCorte!.ventaTrans;
    double deposito = _selectedCorte!.deposito!;
    double retiro = _selectedCorte!.retiro!;
    double gasto = _selectedCorte!.gasto!;
    double corteFinal = _selectedCorte!.corteFinal!;
    DateTime? fechaApertura = _selectedCorte!.fechaApertura;

    await CorteCajaTicket.imprimirTicket(
      fondo: fondo,
      fondoDiaSig: fondoDiaSig,
      ventaEfe: ventaEfe!,
      ventaTarj: ventaTarj!,
      ventaTrans: ventaTransf!,
      deposito: deposito,
      retiro: retiro,
      gasto: gasto,
      corteFinal: corteFinal,
      fechaApertura: fechaApertura,
    );
  }

  @override
  void dispose() {
    _ventaEfeController.dispose();
    _ventaTarjController.dispose();
    _ventaTransfController.dispose();
    _totalVentaController.dispose();
    _fondoController.dispose();
    corteFinal = 0.0;
    super.dispose();
  }

  Map<String, double> obtenerValoresFondo() {
    double fondo =
        double.tryParse(fondoController.text.replaceAll(',', '')) ?? 0;
    double fondoDiaSig =
        double.tryParse(fondoDiaSigController.text.replaceAll(',', '')) ?? 0;
    double ventaEfe =
        (double.tryParse(ventaEfeController.text.replaceAll(',', '')) ?? 0);

    return {
      'fondo': fondo,
      'fondoDiaSig': fondoDiaSig,
      'ventaEfe': ventaEfe,
    };
  }

  Future<List<Pedido>> fetchPedidosCancelados(String idCorteCaja) async {
    var db = await RepoService().db;
    List<Map<String, dynamic>> maps = await db!.query(
      'Pedido',
      where: 'idCorteCaja = ? AND eliminado IS NULL AND estatus = ?',
      whereArgs: [idCorteCaja, 'CANCELADO'],
    );
    return maps.map((map) => Pedido.fromJson(map)).toList();
  }

  Future<DateTime?> fetchHoraLocal() async {
    try {
      final url = Uri.parse('https://worldtimeapi.org/api/ip');
      final response = await http.get(url);

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        final dateTimeString = data['datetime'] as String;
        DateTime serverDateTime = DateTime.parse(dateTimeString);
        final localTime = serverDateTime.toLocal();

        return localTime;
      } else {
        print("Error al consultar la hora: ${response.statusCode}");
        return null;
      }
    } catch (e) {
      print("Excepción al consultar la hora: $e");
      return null;
    }
  }

  Future<void> fetchPedidosConPropinas(String idCorteCaja) async {
    RepoService<Pedido> repoPedido = RepoService<Pedido>();

    List<Pedido> pedidosPropina =
        await repoPedido.obtenerPedidosConPropinasPorCorte(idCorteCaja);

    double sumaPropinas = 0.0;
    for (var pedido in pedidosPropina) {
      double sumLocal = pedido.propinas.fold(
        0.0,
        (prev, propina) => prev + (propina.cantidad ?? 0.0),
      );
      sumaPropinas += sumLocal;
    }
    _pedidosConPropinas = pedidosPropina;
    _totalPropinas = sumaPropinas;

    notifyListeners();
  }
}
