import 'dart:convert';
import 'package:intl/intl.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import '../models/models.dart';

class RepoService<T> {
  static int dbVersion = 18;
  static String dbName = 'joshipos026.db';
  static const String id = Basico.identificadorWeb;
  static const String idLocal = Basico.identificadorLocal;
  static Database? _db;
  final Map<String, dynamic> contexto = {
    'Categoria': CategoriaProducto(),
    'Producto': Producto(),
    'Pedido': Pedido(productos: []),
    'PedidoProducto': PedidoProducto(),
    'Gasto': Gasto(),
    'Deposito': Deposito(),
    'CorteCaja': CorteCaja(),
  };

  Future<Database?> get db async {
    if (_db != null) return _db;

    _db = await databaseInit();
    return _db;
  }

  Future<Database> databaseInit() async {
    String dir = (await getApplicationDocumentsDirectory()).path;
    String path = join(dir, dbName);
    return await openDatabase(
      path,
      version: dbVersion,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
    );
  }

  _onCreate(Database db, int version) async {
    contexto.forEach((String nombre, dynamic modelo) async {
      Map<String, dynamic> model = json.decode(json.encode(modelo.toJson()));
      String sql =
          "CREATE TABLE ${modelo.runtimeType.toString()} (id INTEGER PRIMARY KEY AUTOINCREMENT";
      model.forEach((String key, dynamic value) {
        if (key != "id") {
          String tipo = value.runtimeType.toString();
          String sqlType = "";
          if (equals(tipo, 'int')) {
            sqlType = "INTEGER";
          } else if (equals(tipo, 'double')) {
            sqlType = "REAL";
          } else if (equals(tipo, 'bool')) {
            sqlType = "BOOLEAN";
          } else {
            sqlType = "TEXT";
          }

          sql += ", $key $sqlType";
        }
      });
      sql += ")";
      await db.execute(sql);

      await db.execute('''
        CREATE TABLE PedidoProductoTopping (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          idPedidoProducto INTEGER,
          idTopping INTEGER,
          FOREIGN KEY (idPedidoProducto) REFERENCES PedidoProducto(id),
          FOREIGN KEY (idTopping) REFERENCES Producto(id)
        )
        ''');

      if (modelo.runtimeType.toString() == 'Pedido') {
        await db.execute(
            'ALTER TABLE Pedido ADD COLUMN descuento INTEGER DEFAULT 0');
      }

      await db.execute('''
    CREATE TABLE Descuento (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      porcentaje INTEGER
    )
  ''');

      await db.insert('Descuento', {'porcentaje': 0});
      await db.insert('Descuento', {'porcentaje': 5});
      await db.insert('Descuento', {'porcentaje': 10});
      await db.insert('Descuento', {'porcentaje': 15});
      await db.insert('Descuento', {'porcentaje': 20});
      await db.insert('Descuento', {'porcentaje': 25});
      await db.insert('Descuento', {'porcentaje': 30});
    });

    await db.execute('''
      CREATE TABLE ProductoTopping (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        idProducto INTEGER,
        idTopping INTEGER,
        FOREIGN KEY (idProducto) REFERENCES Producto(id),
        FOREIGN KEY (idTopping) REFERENCES Producto(id)
      )
    ''');

    await db.execute('''
      CREATE TABLE Variable (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        nombre TEXT,
        clave TEXT,
        descripcion TEXT,
        activo BOOLEAN,
        idLocal INTEGER,
        eliminado TEXT
      )
    ''');

    await db.execute('''
          ALTER TABLE Pedido ADD COLUMN idWeb INTEGER;
        ''');
    await db.execute('''
          ALTER TABLE Pedido ADD COLUMN sincronizado TEXT;
        ''');
    await db.execute('''
          ALTER TABLE PedidoProducto ADD COLUMN idWeb INTEGER;
        ''');
    await db.execute('''
          ALTER TABLE PedidoProducto ADD COLUMN sincronizado TEXT;
        ''');

    await db.insert('Variable', {
      'nombre': 'Imprimir Ticket Cocina',
      'clave': 'ticket_cocina',
      'descripcion':
          'Variable para imprimir ticket de cocina automaticamente al momento de generar un pedido',
      'activo': 1,
      'idLocal': -1,
    });

    await db.insert('Variable', {
      'nombre': 'Imprimir Ticket Venta',
      'clave': 'ticket_venta',
      'descripcion':
          'Variable para imprimir ticket de venta automaticamente al momento de generar un pedido',
      'activo': 1,
      'idLocal': -1,
    });

    await db.insert('Variable', {
      'nombre': 'Sucursal',
      'clave': 'PRUEBA',
      'descripcion': 'Sucursal Prueba',
      'activo': 1,
      'idLocal': -1,
    });
  }

  void _onUpgrade(Database db, int oldVersion, int newVersion) async {
    while (oldVersion < newVersion) {
      switch (oldVersion) {
        case 1:
          await db.execute(
              "ALTER TABLE CategoriaProducto ADD COLUMN esToping INTEGER DEFAULT 0");

          await db.execute(
              "ALTER TABLE CategoriaProducto ADD COLUMN descripcion TEXT DEFAULT ''");

          await db.execute(
              "ALTER TABLE CategoriaProducto ADD COLUMN maximo INTEGER");

          await db.insert('CategoriaProducto', {
            'nombre': 'BASE PRODUCTO',
            'descripcion': 'Base del producto',
            'esToping': 1,
            'maximo': 1,
          });

          await db.insert('CategoriaProducto', {
            'nombre': 'SALSAS PRODUCTO',
            'descripcion': 'Elige tus salsas (Máx. 2)',
            'esToping': 1,
            'maximo': 2,
          });

          await db.insert('CategoriaProducto', {
            'nombre': 'ADEREZO PRODUCTO',
            'descripcion': 'Elige tu aderezo (Máx. 2)',
            'esToping': 1,
            'maximo': 2,
          });

          await db.insert('CategoriaProducto', {
            'nombre': 'TOPPING PRODUCTO',
            'descripcion': 'Elige tus toppings (Máx. 2)',
            'esToping': 1,
            'maximo': 2,
          });

          await db.insert('Producto', {
            'nombre': 'Papa Gajo',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'Papa Regilla',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'Papa Curly',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'Papa Smile',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'Papa Francesa',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'BBQ',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'HOTBBQ',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'BUFFALO',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'TERIYAKI',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'PARMESAN GARLIC',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'QUESO AMARILLO',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'RANCH',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'CHIPOTLE',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'ADEREZO JALAPEÑO',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'KETCHUP',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'JALAPEÑO',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'QUESO BLANCO',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'TAKIS',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'RUFFLES',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'QUESO PARMESANO',
            'precio': 0,
          });

          await db.insert('Producto', {
            'nombre': 'ELOTE',
            'precio': 0,
          });

          break;

        case 2:
          await db.execute('''
            CREATE TABLE ProductoTopping (
              id INTEGER PRIMARY KEY AUTOINCREMENT,
              idProducto INTEGER,
              idTopping INTEGER,
              FOREIGN KEY (idProducto) REFERENCES Producto(id),
              FOREIGN KEY (idTopping) REFERENCES Producto(id)
            )
          ''');
          break;

        case 3:
          await db.execute('''
            CREATE TABLE PedidoProductoTopping (
              id INTEGER PRIMARY KEY AUTOINCREMENT,
              idPedidoProducto INTEGER,
              idTopping INTEGER,
              FOREIGN KEY (idPedidoProducto) REFERENCES PedidoProducto(id),
              FOREIGN KEY (idTopping) REFERENCES Producto(id)
            )
          ''');
          break;

        case 4:
          await db.execute('''
          ALTER TABLE Pedido ADD COLUMN descuento INTEGER DEFAULT 0
        ''');
          break;

        case 5:
          await db.execute('''
            CREATE TABLE IF NOT EXISTS Descuento (
              id INTEGER PRIMARY KEY AUTOINCREMENT,
              porcentaje INTEGER
            )
            ''');

          await db.insert('Descuento', {'porcentaje': 0});
          await db.insert('Descuento', {'porcentaje': 5});
          await db.insert('Descuento', {'porcentaje': 10});
          await db.insert('Descuento', {'porcentaje': 15});
          await db.insert('Descuento', {'porcentaje': 20});
          await db.insert('Descuento', {'porcentaje': 25});
          await db.insert('Descuento', {'porcentaje': 30});
          break;

        case 6:
          await db.execute('''
          ALTER TABLE Pedido ADD COLUMN tipoPago TEXT DEFAULT '';
        ''');
          await db.execute('''
          ALTER TABLE Pedido ADD COLUMN cantEfectivo REAL DEFAULT 0;
        ''');
          await db.execute('''
          ALTER TABLE Pedido ADD COLUMN cantTarjeta REAL DEFAULT 0;
        ''');
          await db.execute('''
          ALTER TABLE Pedido ADD COLUMN cantTransferencia REAL DEFAULT 0;
        ''');
          break;

        case 7:
          await db.execute('''
            CREATE TABLE Variable (
              id INTEGER PRIMARY KEY AUTOINCREMENT,
              nombre TEXT,
              clave TEXT,
              descripcion TEXT,
              activo BOOLEAN,
              idLocal INTEGER,
              eliminado TEXT
            )
          ''');
          break;

        // case 8:
        //   await db.execute('''
        //   ALTER TABLE Producto ADD COLUMN toping INTEGER DEFAULT 0;
        // ''');
        //   break;

        case 9:
          await db.execute('''
          ALTER TABLE Pedido ADD COLUMN idWeb INTEGER;
        ''');
          await db.execute('''
          ALTER TABLE Pedido ADD COLUMN sincronizado TEXT;
        ''');
          await db.execute('''
          ALTER TABLE PedidoProducto ADD COLUMN idWeb INTEGER;
        ''');
          await db.execute('''
          ALTER TABLE PedidoProducto ADD COLUMN sincronizado TEXT;
        ''');
          break;

        case 10:
          await db.execute('''
          update Pedido set sincronizado = null, peticion = strftime('%Y-%m-%dT%H:%M:%S',
                datetime(substr(peticion, 7, 4) || '-' ||
                substr(peticion, 4, 2) || '-' ||
                substr(peticion, 1, 2) || ' ' ||
                substr(peticion, 12, 8) || ' -07:00', 'localtime', '+07:00'))
          WHERE strftime('%Y-%m-%dT%H:%M:%S',
                datetime(substr(peticion, 7, 4) || '-' ||
                substr(peticion, 4, 2) || '-' ||
                substr(peticion, 1, 2) || ' ' ||
                substr(peticion, 12, 8) || ' -07:00', 'localtime', '+07:00')) is not null
        ''');
          break;

        case 11:
          await db.execute('DROP TABLE IF EXISTS Producto');

          //Se tiene que crear nuevamente para que precio sea Double
          await db.execute('''
          CREATE TABLE Producto (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            idCategoria INTEGER,
            idLocal INTEGER,
            nombre TEXT,
            descripcion TEXT,
            imagen TEXT,
            venta INTEGER,
            existencia INTEGER,
            precio REAL,
            verMenu INTEGER,
            codigo TEXT,
            descuento TEXT,
            toping INTEGER,
            creado TEXT,
            modificado TEXT,
            eliminado TEXT
          )
        ''');
          await db.execute('DELETE FROM CategoriaProducto');
          await db.execute('''
            ALTER TABLE CategoriaProducto ADD COLUMN creado TEXT;
          ''');
          await db.execute('''
            ALTER TABLE CategoriaProducto ADD COLUMN modificado TEXT;
          ''');

          break;

        case 12:
          await db.execute('''
            ALTER TABLE Producto ADD COLUMN activo INTEGER;
          ''');

          break;

        case 13:
          await db.execute('''
            ALTER TABLE Producto ADD COLUMN activo INTEGER;
          ''');

          break;

        case 14:
          await db.execute('''
            ALTER TABLE ProductoTopping ADD COLUMN idCategoria INTEGER;
          ''');
          await db.execute('''
            ALTER TABLE ProductoTopping ADD COLUMN creado text;
          ''');
          await db.execute('''
            ALTER TABLE ProductoTopping ADD COLUMN modificado text;
          ''');
          await db.execute('''
            ALTER TABLE ProductoTopping ADD COLUMN eliminado text;
          ''');

          break;

        case 15:
          await db.execute('''
          CREATE TABLE Sucursal (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nombre TEXT,
            descripcion TEXT,
            direccion TEXT,
            ciudad TEXT,
            activo INTEGER,
            clave TEXT,
            eliminado TEXT,
            creado TEXT,
            modificado TEXT,
            idLocal INTEGER,
            seleccionado INTEGER
          )
        ''');

          break;

        case 16:
          await db.execute('''
          CREATE TABLE Usuario (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nombre TEXT,
            apellidos TEXT,
            correo TEXT,
            celular TEXT,
            celularPersonal TEXT,
            rol INTEGER,
            genero BOOLEAN,
            estatus INTEGER,
            imagen TEXT,
            rfc TEXT,
            razonSocial TEXT,
            calle TEXT,
            numeroExterior TEXT,
            colonia TEXT,
            codigoPostal TEXT,
            idCiudad INTEGER,
            idEstado INTEGER,
            idSucursal INTEGER,
            turno TEXT,
            eliminado TEXT,
            creado TEXT,
            modificado TEXT,
            idLocal INTEGER
          )
        ''');

          await db.execute('''
          CREATE TABLE Permiso (
            id TEXT PRIMARY KEY,
            idModulo TEXT,
            nombre TEXT,
            descripcion TEXT,
            eliminado TEXT,
            creado TEXT,
            modificado TEXT
          )
        ''');

          await db.execute('''
          CREATE TABLE UsuarioPermiso (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            idUsuario INTEGER,
            idPermiso TEXT,
            asignado TEXT,
            modificado TEXT,
            eliminado TEXT,
            idLocal INTEGER
          )
        ''');

          await db.execute('''
            ALTER TABLE ProductoTopping ADD COLUMN idLocal INTEGER;
          ''');

          await db.execute('''
            ALTER TABLE PedidoProductoTopping ADD COLUMN idLocal INTEGER;
          ''');

          await db.execute('''
            ALTER TABLE PedidoProductoTopping ADD COLUMN idCategoria INTEGER;
          ''');

          break;

        case 17:
          await db.execute('''
            ALTER TABLE CategoriaProducto ADD COLUMN minimo INTEGER;
          ''');

          break;
      }
      oldVersion++;
    }
  }

  Future<int> guardar(T model) async {
    try {
      //print("Guardando modelo en la base de datos: ${model.runtimeType}");

      // Convertir el modelo a JSON para la base de datos
      String modelo = json.encode(model, toEncodable: toEncodable);
      //print("Modelo convertido a JSON: $modelo");

      Map<String, dynamic> modelMap = json.decode(modelo);
      var dbClient = await db;
      String nombreTabla = model.runtimeType.toString();

      // Verificar si el modelo es de tipo Permiso (con id de tipo String)
      if (model is Permiso) {
        String? id = modelMap['id'];

        if (id == null || id.isEmpty) {
          throw Exception('El ID del permiso no puede ser nulo o vacío');
        }

        List<Map> existing = await dbClient!.query(
          nombreTabla,
          where: 'id = ?',
          whereArgs: [id],
        );

        if (existing.isNotEmpty) {
          //print("Actualizando registro existente con ID: $id");
          await dbClient!.update(
            nombreTabla,
            modelMap,
            where: 'id = ?',
            whereArgs: [id],
          );
        } else {
          print(
              "Insertando nuevo registro en la tabla $nombreTabla con ID: $id");
          await dbClient!.insert(nombreTabla, modelMap);
        }

        return 1;
      } else {
        int? id = modelMap['id'];

        if (id == null || id == 0) {
          modelMap.remove('id');
        }

        List<Map> existing = id != null && id > 0
            ? await dbClient!
                .query(nombreTabla, where: 'id = ?', whereArgs: [id])
            : [];

        if (existing.isNotEmpty) {
          print(
              "Actualizando registro existente con ID: $id y modificado: ${modelMap['modificado']}");
          await dbClient!.update(
            nombreTabla,
            modelMap,
            where: 'id = ?',
            whereArgs: [id],
          );
        } else {
          print("Insertando nuevo registro en la tabla $nombreTabla");
          id = await dbClient!.insert(nombreTabla, modelMap);
        }

        return id!;
      }
    } catch (e) {
      print('Error al guardar en dynamic: $e');
      return 0;
    }
  }

  void asignarFechasLocalmente(Basico model) {
    DateTime ahora = DateTime.now();
    if (model.creado == null) {
      model.creado = ahora.toUtc();
    }
    model.modificado = ahora.toUtc();
  }

  Future<void> _guardarToppings(
      Database db, int idProducto, List<Producto>? topings) async {
    await db.delete('ProductoTopping',
        where: 'idProducto = ?', whereArgs: [idProducto]);

    if (topings != null) {
      for (var topping in topings) {
        await db.insert('ProductoTopping', {
          'idProducto': idProducto,
          'idTopping': topping.id,
        });
      }
    }
  }

  Future<int> guardarLocal(T model) async {
    var dbClient = await db;
    String nombreTabla = model.runtimeType.toString();
    String modelo = json.encode(model, toEncodable: toEncodable);
    Map<String, dynamic> modelMap = json.decode(modelo);

    if (nombreTabla == "PedidoProductoTopping") {
      modelMap.remove('idLocal');
      modelMap.remove('eliminado');
    }

    if (modelMap['id'] == null || modelMap['id'] == 0) {
      modelMap.remove('id');
    }

    int resultado;
    int? identificadorLocal = modelMap[Basico.identificadorLocal];

    if (identificadorLocal != null && identificadorLocal > 0) {
      resultado = await dbClient!.update(
        nombreTabla,
        modelMap,
        where: "$idLocal = ?",
        whereArgs: [identificadorLocal],
      );
    } else {
      resultado = await dbClient!.insert(nombreTabla, modelMap);
      var rawQuery =
          await dbClient.rawQuery("SELECT last_insert_rowid() as id");
      if (rawQuery.isNotEmpty) {
        resultado = int.parse(rawQuery.first["id"].toString());
        modelMap[Basico.identificadorLocal] = resultado;
      }
    }

    if (model is Pedido) {
      model.id = resultado;
    }

    if (model is Producto) {
      await _guardarToppings(dbClient!, model.id!, model.topings);
    }

    return resultado;
  }

  dynamic toEncodable(dynamic item) {
    return item.toJson();
  }

  Future<List<T>> obtenerTodos({String orderBy = 'id DESC'}) async {
    var db = await this.db;
    String tableName = T.toString();
    var result = await db!
        .query(tableName, where: 'eliminado IS NULL', orderBy: orderBy);
    return result.map((map) => fromMap<T>(map)).toList();
  }

  T fromMap<T>(Map<String, dynamic> map) {
    switch (T) {
      case Pedido:
        return Pedido.fromJson(map) as T;
      case Producto:
        return Producto.fromJson(map) as T;
      case Usuario:
        return Usuario.fromJson(map) as T;
      default:
        throw Exception('Tipo no soportado');
    }
  }

  Future<Pedido?> obtenerPorId(int id) async {
    Database? dbClient = await db;
    List<Map> maps =
        await dbClient!.query('Pedido', where: 'id = ?', whereArgs: [id]);
    if (maps.isNotEmpty) {
      return Pedido.fromJson(Map<String, dynamic>.from(maps.first));
    }
    return null;
  }

  Future<List<PedidoProducto>> obtenerPorIdPedido(int idPedido) async {
    Database? dbClient = await db;
    List<Map> maps = await dbClient!
        .query('PedidoProducto', where: 'idPedido = ?', whereArgs: [idPedido]);
    return maps
        .map((map) => PedidoProducto.fromJson(Map<String, dynamic>.from(map)))
        .toList();
  }

  Future<List<Deposito>> obtenerDepositosPorIdCorteCaja(int idCorteCaja) async {
    var dbClient = await db;
    List<Map<String, dynamic>> maps = await dbClient!.query(
      'Deposito',
      where: 'idCorteCaja = ?',
      whereArgs: [idCorteCaja],
    );
    return maps.map((map) => Deposito.fromJson(map)).toList();
  }

  Future<List<Gasto>> obtenerGastosPorIdCorteCaja(int idCorteCaja) async {
    var dbClient = await db;
    List<Map<String, dynamic>> maps = await dbClient!.query(
      'Gasto',
      where: 'idCorteCaja = ?',
      whereArgs: [idCorteCaja],
    );
    return maps.map((map) => Gasto.fromJson(map)).toList();
  }

  Future<int> contarPedidos() async {
    Database? dbClient = await db;
    var result = await dbClient!.rawQuery('SELECT COUNT(*) FROM Pedido');
    return Sqflite.firstIntValue(result) ?? 0;
  }

  Future<List<Pedido>> obtenerPedidosPaginados(int limit, int offset) async {
    Database? dbClient = await db;
    List<Map<String, dynamic>> result = await dbClient!
        .query('Pedido', limit: limit, offset: offset, orderBy: 'id DESC');
    return result.map((map) => Pedido.fromJson(map)).toList();
  }

  Future<Producto?> obtenerProductoPorId(int idProducto) async {
    Database? dbClient = await db;
    List<Map> maps = await dbClient!
        .query('Producto', where: 'id = ?', whereArgs: [idProducto]);
    if (maps.isNotEmpty) {
      return Producto.fromJson(Map<String, dynamic>.from(maps.first));
    }
    return null;
  }

  Future<List<int>> obtenerToppingsPorProducto(int idProducto) async {
    var dbClient = await db;
    var result = await dbClient!.query(
      'ProductoTopping',
      where: 'idProducto = ?',
      whereArgs: [idProducto],
    );
    return result.map((map) => map['idTopping'] as int).toList();
  }

  Future<List<PedidoProductoTopping>> obtenerToppingsPorPedidoProducto(
      int idPedidoProducto) async {
    var dbClient = await db;
    List<Map> maps = await dbClient!.query('PedidoProductoTopping',
        where: 'idPedidoProducto = ?', whereArgs: [idPedidoProducto]);

    if (maps.isNotEmpty) {
      return maps
          .map((map) =>
              PedidoProductoTopping.fromJson(Map<String, dynamic>.from(map)))
          .toList();
    }
    return [];
  }

  Future<int> obtenerProximoFolio() async {
    var dbClient = await db;
    var result = await dbClient!.rawQuery(
        'SELECT MAX(CAST(folio AS INTEGER)) as last_folio FROM Pedido');
    if (result.isNotEmpty && result.first["last_folio"] != null) {
      return int.tryParse(result.first["last_folio"].toString())! + 1;
    }
    return 1;
  }

  Future<List<T>> buscarPorFolio(String folio) async {
    var dbClient = await db;
    List<Map<String, dynamic>> maps = await dbClient!.query(
      'Pedido',
      where: 'folio LIKE ?',
      whereArgs: ['%$folio%'],
    );
    return maps.map((map) => fromMap<T>(map)).toList();
  }

  Future<List<Pedido>> buscarPorFecha(
      DateTime startDate, DateTime endDate) async {
    var dbClient = await db;

    String startDateString = startDate.toIso8601String();
    String endDateString = endDate.toIso8601String();

    print(
        'Ejecutando consulta: SELECT * FROM Pedido WHERE peticion BETWEEN $startDateString AND $endDateString');

    List<Map<String, dynamic>> maps = await dbClient!.rawQuery('''
    SELECT * FROM Pedido 
    WHERE peticion BETWEEN ? AND ?
  ''', [startDateString, endDateString]);

    print('Resultado de la consulta: ${maps.length} pedidos encontrados.');

    return maps.map((map) => Pedido.fromJson(map)).toList();
  }

  Future<List<CorteCaja>> buscarPorFechaCorte(
      DateTime startDate, DateTime endDate) async {
    var dbClient = await db;
    String startDateString = DateFormat('dd-MM-yyyy').format(startDate);
    String endDateString = DateFormat('dd-MM-yyyy 23:59:59').format(endDate);

    List<Map<String, dynamic>> maps = await dbClient!.query(
      'CorteCaja',
      where: 'fecha BETWEEN ? AND ?',
      whereArgs: [startDateString, endDateString],
    );
    return maps.map((map) => CorteCaja.fromJson(map)).toList();
  }

  Future<void> eliminar<T>(int id) async {
    var dbClient = await db;
    String tableName = T.toString();
    await dbClient!.delete(tableName, where: 'id = ?', whereArgs: [id]);
  }

  Future<List<Descuento>> obtenerTodosDescuentos() async {
    var dbClient = await db;
    var result = await dbClient!.query('Descuento', orderBy: 'porcentaje ASC');
    return result.map((map) => Descuento.fromJson(map)).toList();
  }

  Future<int> guardarDescuento(Descuento descuento) async {
    var dbClient = await db;
    if (descuento.id != null && descuento.id! > 0) {
      return await dbClient!.update(
        'Descuento',
        descuento.toJson(),
        where: 'id = ?',
        whereArgs: [descuento.id],
      );
    } else {
      return await dbClient!.insert('Descuento', descuento.toJson());
    }
  }

  Future<int> eliminarDescuento(int id) async {
    var dbClient = await db;
    return await dbClient!
        .delete('Descuento', where: 'id = ?', whereArgs: [id]);
  }

  Future<Variable?> obtenerPorNombre(String nombre) async {
    var dbClient = await db;
    List<Map<String, dynamic>> maps = await dbClient!.query(
      'Variable',
      where: 'nombre = ?',
      whereArgs: [nombre],
    );

    if (maps.isNotEmpty) {
      return Variable.fromJson(Map<String, dynamic>.from(maps.first));
    }

    return null;
  }

  Future<List<Pedido>> obtenerPedidosOrdenadosPorFecha() async {
    var dbClient = await db;

    String orderBy =
        "datetime(substr(peticion, 7, 4) || '-' || substr(peticion, 4, 2) || '-' || substr(peticion, 1, 2) || ' ' || substr(peticion, 12)) ASC";

    List<Map<String, dynamic>> result = await dbClient!.query(
      'Pedido',
      where: 'sincronizado IS NULL',
      orderBy: orderBy,
    );

    return result.map((map) => Pedido.fromJson(map)).toList();
  }

  Future<void> sincronizarCategorias(
      List<CategoriaProducto> categoriasApi) async {
    var db = await RepoService().db;

    var categoriasLocalesQuery = await db!.query('CategoriaProducto');
    List<CategoriaProducto> categoriasLocales = categoriasLocalesQuery
        .map((e) => CategoriaProducto.fromJson(e))
        .toList();

    for (var categoriaApi in categoriasApi) {
      var categoriaLocal = categoriasLocales.firstWhere(
        (categoria) => categoria.id == categoriaApi.id,
        orElse: () => CategoriaProducto(),
      );

      if (categoriaLocal.id != 0 &&
          categoriaApi.modificado != null &&
          (categoriaLocal.modificado == null ||
              categoriaApi.modificado!.isAfter(categoriaLocal.modificado!))) {
        await RepoService().guardar(categoriaApi);
      } else if (categoriaLocal.id == 0) {
        await RepoService().guardar(categoriaApi);
      }
    }
  }

  Future<void> sincronizarProductos(List<Producto> productosApi) async {
    var db = await RepoService().db;

    // // Print del JSON recibido
    // print(
    //     "Productos API recibidos: ${productosApi.map((e) => e.toJson()).toList()}");

    var productosLocalesQuery = await db!.query('Producto');
    List<Producto> productosLocales =
        productosLocalesQuery.map((e) => Producto.fromJson(e)).toList();

    for (var productoApi in productosApi) {
      // Validar que el ID del producto no sea nulo
      if (productoApi.id == null) {
        print("Producto con ID nulo, se omite: ${productoApi.nombre}");
        continue; // Ignorar productos sin ID
      }

      // Buscar el producto localmente
      var productoLocal = productosLocales.firstWhere(
        (producto) => producto.id == productoApi.id,
        orElse: () =>
            Producto(), // Si no existe el producto, devolver uno nuevo con id 0
      );

      if (productoLocal.id == 0) {
        print("Insertando nuevo producto: ${productoApi.nombre}");
        await RepoService().guardar(productoApi);
      } else if (productoApi.modificado != null &&
          (productoLocal.modificado == null ||
              productoApi.modificado!.isAfter(productoLocal.modificado!))) {
        print("Actualizando producto: ${productoApi.nombre}");
        await RepoService().guardar(productoApi);
      } else {
        // Producto sin cambios
        // print(
        //     "Producto sin cambios o datos insuficientes: ${productoApi.nombre}");
      }
    }
  }

  Future<void> sincronizarProductoTopping(
      List<ProductoTopping> toppingsApi) async {
    var db = await RepoService().db;

    await db!.delete('ProductoTopping');

    for (var toppingApi in toppingsApi) {
      await RepoService().guardar(toppingApi);
    }

    print("Todos los registros de ProductoTopping han sido sincronizados.");
  }

  Future<void> sincronizarSucursales(List<Sucursal> sucursalesApi) async {
    var db = await RepoService().db;

    var sucursalesLocalesQuery = await db!.query('Sucursal');
    List<Sucursal> sucursalesLocales =
        sucursalesLocalesQuery.map((e) => Sucursal.fromJson(e)).toList();

    for (var sucursalApi in sucursalesApi) {
      var sucursalLocal = sucursalesLocales.firstWhere(
        (sucursal) => sucursal.id == sucursalApi.id,
        orElse: () => Sucursal(),
      );

      if (sucursalLocal.id != 0 &&
          sucursalApi.modificado != null &&
          (sucursalLocal.modificado == null ||
              sucursalApi.modificado!.isAfter(sucursalLocal.modificado!))) {
        await RepoService().guardar(sucursalApi);
      } else if (sucursalLocal.id == 0) {
        await RepoService().guardar(sucursalApi);
      }
    }
  }

  Future<void> sincronizarPermisos(List<Permiso> permisosApi) async {
    var db = await RepoService().db;
    var permisosLocalesQuery = await db!.query('Permiso');
    List<Permiso> permisosLocales =
        permisosLocalesQuery.map((e) => Permiso.fromJson(e)).toList();

    for (var permisoApi in permisosApi) {
      var permisoLocal = permisosLocales.firstWhere(
        (permiso) => permiso.id == permisoApi.id,
        orElse: () => Permiso(),
      );

      if (permisoLocal.id != null &&
          permisoApi.modificado != null &&
          (permisoLocal.modificado == null ||
              permisoApi.modificado!.isAfter(permisoLocal.modificado!))) {
        print('Actualizando permiso con ID: ${permisoApi.id}');
        await RepoService().guardar(permisoApi);
      } else if (permisoLocal.id == null) {
        print('Insertando nuevo permiso con ID: ${permisoApi.id}');
        await RepoService().guardar(permisoApi);
      } else {
        //print('Permiso sin cambios: ${permisoApi.id}');
      }
    }
  }

  Future<void> sincronizarUsuarios(List<Usuario> usuariosApi) async {
    var db = await RepoService().db;

    var usuariosLocalesQuery = await db!.query('Usuario');
    List<Usuario> usuariosLocales =
        usuariosLocalesQuery.map((e) => Usuario.fromJson(e)).toList();

    for (var usuarioApi in usuariosApi) {
      var usuarioLocal = usuariosLocales.firstWhere(
        (usuario) => usuario.id == usuarioApi.id,
        orElse: () => Usuario(),
      );

      // Comprobar si realmente se necesita actualizar el usuario basado en la fecha de modificado
      if (usuarioLocal.id != 0 &&
          usuarioApi.modificado != null &&
          (usuarioLocal.modificado == null ||
              usuarioApi.modificado!.isAfter(usuarioLocal.modificado!))) {
        print('Actualizando usuario con ID: ${usuarioApi.id}');
        await RepoService().guardar(usuarioApi);

        // Comparar permisos antes de actualizarlos
        await _actualizarPermisosUsuario(
            db, usuarioApi.id!, usuarioApi.permisos!);
      } else if (usuarioLocal.id == 0) {
        print('Insertando nuevo usuario con ID: ${usuarioApi.id}');
        await RepoService().guardar(usuarioApi);

        // Insertar los permisos correspondientes
        await _guardarPermisosUsuario(db, usuarioApi.id!, usuarioApi.permisos!);
      } else {
        //print('Usuario sin cambios: ${usuarioApi.id}');
      }
    }
  }

  Future<void> _actualizarPermisosUsuario(
      Database db, int idUsuario, List<String> permisosApi) async {
    // Obtener los permisos actuales del usuario
    var permisosLocalesQuery = await db.query(
      'UsuarioPermiso',
      where: 'idUsuario = ?',
      whereArgs: [idUsuario],
    );
    List<String> permisosLocales = permisosLocalesQuery
        .map((permiso) => permiso['idPermiso'] as String)
        .toList();

    // Comparar los permisos del API con los locales
    bool sonIguales = _listasIguales(permisosLocales, permisosApi);

    if (!sonIguales) {
      // Si los permisos no son iguales, actualizarlos
      print('Actualizando permisos del usuario con ID: $idUsuario');
      await _guardarPermisosUsuario(db, idUsuario, permisosApi);
    } else {
      print('Permisos del usuario con ID: $idUsuario no han cambiado.');
    }
  }

  Future<void> _guardarPermisosUsuario(
      Database db, int idUsuario, List<String> permisos) async {
    // Eliminar los permisos actuales solo si hay cambios
    await db.delete('UsuarioPermiso',
        where: 'idUsuario = ?', whereArgs: [idUsuario]);

    // Insertar los nuevos permisos
    for (var idPermiso in permisos) {
      await db.insert('UsuarioPermiso', {
        'idUsuario': idUsuario,
        'idPermiso': idPermiso,
      });
    }
  }

  bool _listasIguales(List<String> lista1, List<String> lista2) {
    if (lista1.length != lista2.length) return false;
    lista1.sort();
    lista2.sort();
    for (int i = 0; i < lista1.length; i++) {
      if (lista1[i] != lista2[i]) return false;
    }
    return true;
  }

  Future<String?> obtenerClaveSucursalSeleccionada() async {
    var db = await this.db;
    List<Map<String, dynamic>> queryResult = await db!.query(
      'Sucursal',
      where: 'seleccionado = ? AND eliminado IS NULL',
      whereArgs: [1],
      limit: 1,
    );

    if (queryResult.isNotEmpty) {
      Sucursal sucursalSeleccionada = Sucursal.fromJson(queryResult.first);
      return sucursalSeleccionada.clave;
    }

    return null;
  }
}
