import 'dart:async';
import 'dart:io';

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'Data.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:sn_progress_dialog/sn_progress_dialog.dart';

late http.Response loginResponse;

late Database cacheDb;
late String username;
List<Category> categories = [];
List<TaskType> taskTypes = [];
List<Activity> activities=[];
bool offline = true;

Future<http.Response> login(String _username, String password) async {
  username = _username;
  var device_id = await Settings.UUID();
  try {
    loginResponse = (await http.post(
        Uri.parse('http://161.97.127.136/task_tracker/login.php'),
        body: <String, String>{
          "username": _username,
          "password": password,
          "device_id": device_id ?? 'n/a'
        }));

    if (loginResponse.body.toLowerCase().contains("success")) {
      offline = false;
      username = _username;
    }
  }catch(e){
    offline=true;
  }
  return loginResponse;
}

Future<void> initUserData() async {
  await initCacheDatabase();
  await UpdateCategoriesFromServer();
  await UpdateTaskTypesFromServer();
  await GetCategories(true);
  await GetTaskTypes(true);
  print('Initializing UserData...');
  if (offline) {
    print('Going offline mode.');
  }
}

Future<bool> cacheDbExist() async{
  Directory directory = await getApplicationDocumentsDirectory();
  return databaseFactory.databaseExists(directory.path + 'cache.db');
}

Future<void> updateCatsList() async{
  print('Updating with localCache');
  categories = await GetCategories(true);
  print('Checking if can refresh');
  categories = await GetCategories(false);
}

Future<void> updateTasksList() async{
  print('Updating with localCache');
  taskTypes = await GetTaskTypes(true);
  print('Checking if can refresh');
  taskTypes = await GetTaskTypes(false);
}


Future<void> initCacheDatabase() async {
  Directory directory = await getApplicationDocumentsDirectory();
  print('database at ' + directory.path + 'cache.db');
  cacheDb = await openDatabase(directory.path + 'cache.db', version: 1, onCreate: onCacheDatabaseCreate, onUpgrade: onCacheDatabaseUpgrade);

  await UserOperations.executeQueries();
}

void onCacheDatabaseCreate(Database db, int newVersion) async {
  String CategoriesTableSQL =
      'CREATE TABLE Categories(${Category.colCatId} VARCHAR(255) PRIMARY KEY,${Category.colName} TEXT, ${Category.colColor} TEXT, ${Category.colProductive} INTEGER)';
//  print(CategoriesTableSQL);
  await db.execute(CategoriesTableSQL);
  print("Initiated Categories Table");

  String TaskTableSQL =
      'CREATE TABLE TaskTypes(id TEXT PRIMARY KEY, ${TaskType.colName} TEXT, ${TaskType.colCategory} TEXT, '
      'FOREIGN KEY (${TaskType.colCategory}) REFERENCES Categories(${Category.colCatId}))';
 // print(TaskTableSQL);
  await db.execute(TaskTableSQL);

  String ActivityTableSQL =
      'CREATE TABLE Activities(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Activity.colType} INT, ${Activity.colStartTime} DATETIME, ${Activity.colEndTime} DATETIME, '
      'FOREIGN KEY (${Activity.colType}) REFERENCES TaskTypes(id))';
 // print(ActivityTableSQL);
  await db.execute(ActivityTableSQL);

  String QueriesTableSQL = 'CREATE TABLE Queries(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Queries.colLink} TEXT,${Queries.colData} TEXT)';
//  print(QueriesTableSQL);
  await db.execute(QueriesTableSQL);

  addInitialDataToCache();
 // GetCategories();
}

Future<void> addInitialDataToCache() async{

  await Future.delayed(const Duration(seconds: 1));

  //Insert Initial Entries
  for(Category element in InitialData.getCategories(username)){
    await UserOperations.addCategory(element.name, element.color, element.productive,bulk: true);
  }

  for(TaskType element in InitialData.getTaskTypes(username)){
    await UserOperations.addTaskType(element.name, element.category, bulk: true);
    // Map<String,Object> data = {
    //   TaskType.colName: element.name,
    //   TaskType.colCategory: element.category
    // };
    // await cacheDb.insert('TaskTypes', data);
  }

  UserOperations.executeQueries();

}

void onCacheDatabaseUpgrade(Database db, int oldVersion, int newVersion) async {
  //ValidateCacheDB();
  print('Upgrading CacheDB from ver.$oldVersion to ver.$newVersion');
}


Future<List<Category>> GetCategories(bool forceOffline) async{
  List<Category> _categories = [];
  if(offline || forceOffline){
    //Retreive from cacheDB

  }else{
    //Check if server got updated, If not go for cache
    var android_id = await Settings.UUID();

    //Validate device_id to check updates

    bool catsUpdated = true;
    try{
      http.Response update_response = (await http.post(
          Uri.parse('http://161.97.127.136/task_tracker/check_update.php'),
          body: <String, String>{"username": username, "device_id":android_id??'n/a'}));
      final data = update_response.body.split(',');
      catsUpdated = data[0] == '1';
    }catch(e){
      print(e);
    }

    print("Need to update : ${!catsUpdated}");

    //Update CacheDB
    if(!catsUpdated){
      await UpdateCategoriesFromServer();
    }
  }

  List<Map> cats = await cacheDb.query('Categories');
  print(cats.length);
  for(Map element in cats){
    String? catName = element[Category.colName].toString();
    String? catColor = element[Category.colColor].toString();
    String? catProductive = element[Category.colProductive].toString();
    if(catName==null || catColor==null || catProductive==null){
      print("Something is null!");
      print("name:{$catName}, color:{$catColor}, prod:{$Category.colProductive}");
      continue;
    }
    print("name:{$catName}, color:{$catColor}, prod:{$catProductive}");
    _categories.add(Category(username + catName, catName, catColor, ParseBool(catProductive)));
  }
  categories = _categories;
  return categories;
}

Future<void> UpdateCategoriesFromServer() async{
  print("Updating Categories");
  try {
    http.Response response = (await http.post(
        Uri.parse('http://161.97.127.136/task_tracker/get_categories.php'),
        body: <String, String>{
          "username": username,
          "device_id": await Settings.UUID() ?? 'n/a'
        }));

    print(response.body);
    List<String> data = response.body.split("<td>");
    data.forEach((value) async {
      Map<String, dynamic> cat = jsonDecode(value);
      //print(catData);
      await cacheDb.rawInsert(
          "INSERT OR REPLACE INTO Categories (${Category.colCatId},${Category
              .colName},${Category.colProductive},${Category.colColor}) "
              "VALUES ('${cat['category_id']}','${cat['name']}',${cat['productive']},'${cat['color']}') ");
    });
  }catch(e){
    offline=true;
  }
}

Future<List<TaskType>> GetTaskTypes(bool forceOffline) async{
  List<TaskType> _taskTypes = [];
  if(offline || forceOffline){
    //Retreive from cacheDB

  }else{
    //Check if server got updated, If not go for cache
    var android_id = await Settings.UUID();

    bool updated =true;
    try{
      //Validate device_id to check updates
      http.Response update_response = (await http.post(
          Uri.parse('http://161.97.127.136/task_tracker/check_update.php'),
          body: <String, String>{"username": username, "device_id":android_id??'n/a'}));
      final data = update_response.body.split(',');
      updated = data[1] == '1';
    }catch(e){
      print(e);
    }

    print("Need to update : ${!updated}");

    //Update CacheDB
    if(!updated){
      await UpdateTaskTypesFromServer();
    }
  }

  List<Map> cats = await cacheDb.query('TaskTypes');
  print(cats.length);
  for(Map element in cats){
    String? id = element[TaskType.colId].toString();
    String? name = element[TaskType.colName].toString();
    String? category = element[TaskType.colCategory].toString();
    Category? cat = await getCatFromId(category);
    if(id==null || name==null || category==null){
      print("Something is null!");
      print("name:{$name}, cat:{$category}, prod:{$id}");
      continue;
    }
    print("name:{$name}, cat:{$category}, prod:{$id}");
    _taskTypes.add(TaskType(id,name,category,cat));
  }
  taskTypes = _taskTypes;
  return taskTypes;
}

Future<void> UpdateTaskTypesFromServer() async{
  print("Updating TaskTypes");
  try {
    http.Response response = (await http.post(
        Uri.parse('http://161.97.127.136/task_tracker/get_taskTypes.php'),
        body: <String, String>{
          "username": username,
          "device_id": await Settings.UUID() ?? 'n/a'
        }));

    print(response.body);
    List<String> data = response.body.split("<td>");
    data.forEach((value) async {
      Map<String, dynamic> cat = jsonDecode(value);
      //print(catData);
      await cacheDb.rawInsert(
          "INSERT OR REPLACE INTO TaskTypes (${TaskType.colId},${TaskType
              .colName},${TaskType.colCategory}) "
              "VALUES ('${cat['id']}','${cat['name']}',${cat['category']}) ");
    });
  }catch(e){
    offline=true;
  }
}

Future<List<TaskType>> GetActivities(bool forceOffline) async{
  List<Activity> _activities = [];
  if(offline || forceOffline){
    //Retreive from cacheDB

  }else{
    //Check if server got updated, If not go for cache
    var android_id = await Settings.UUID();

    bool updated =true;
    try{
      //Validate device_id to check updates
      http.Response update_response = (await http.post(
          Uri.parse('http://161.97.127.136/task_tracker/check_update.php'),
          body: <String, String>{"username": username, "device_id":android_id??'n/a'}));
      final data = update_response.body.split(',');
      updated = data[2] == '1';
    }catch(e){
      print(e);
    }

    print("Need to update activities : ${!updated}");

    //Update CacheDB
    if(!updated){
      await UpdateActivitiesFromServer();
    }
  }

  List<Map> cats = await cacheDb.query('Activities');
  print(cats.length);
  for(Map element in cats){
    String? type = element[Activity.colType].toString();
    String? startTime = element[Activity.colStartTime].toString();
    String? endTime = element[Activity.colStartTime].toString();
    TaskType? taskType = await getTaskFromId(type);
    if(type==null || startTime==null || endTime==null || taskType==null){
      print("Something is null!");
      print("TaskType:{$type}, Start Time:{$startTime}, endTime:{$endTime}");
      continue;
    }
    print("TaskType:{$type}, Start Time:{$startTime}, endTime:{$endTime}");
    _activities.add(Activity(taskType, DateTime.parse(startTime), DateTime.parse(endTime)));
  }
  activities = _activities;
  return taskTypes;
}

Future<void> UpdateActivitiesFromServer() async{
  print("Updating TaskTypes");
  try {
    http.Response response = (await http.post(
        Uri.parse('http://161.97.127.136/task_tracker/get_activities.php'),
        body: <String, String>{
          "username": username,
          "device_id": await Settings.UUID() ?? 'n/a'
        }));

    print(response.body);
    List<String> data = response.body.split("<td>");
    data.forEach((value) async {
      Map<String, dynamic> cat = jsonDecode(value);
      //print(catData);
      await cacheDb.rawInsert(
          "INSERT OR REPLACE INTO Activities (${Activity.colType}, ${Activity.colStartTime}, ${Activity.colEndTime}) "
              "VALUES ('${cat['type']}', '${cat['sTime']}','${cat['eTime']}') ");
    });
  }catch(e){
    offline=true;
  }
}

Future<TaskType?> getTaskFromId(String taskId) async{
  // await GetTaskTypes(false);
  TaskType? cat = null;
  taskTypes.forEach((element) async{
    if(element.id == taskId){
      cat= element;

      cat?.cat = await getCatFromId((cat?.category ?? ''));
    }
  });

  return cat;
}

Future<Category?> getCatFromId(String catId) async{
 // await GetTaskTypes(false);
  Category? cat = null;
  categories.forEach((element) {
    if(element.category_id == catId){
      cat= element;
    }
  });

  return cat;
}


//Helpers
class Helpers {
  Future<String?> _getId() async {
    var deviceInfo = DeviceInfoPlugin();
    if (Platform.isIOS) { // import 'dart:io'
      var iosDeviceInfo = await deviceInfo.iosInfo;
      return iosDeviceInfo.identifierForVendor; // unique ID on iOS
    } else {
      var androidDeviceInfo = await deviceInfo.androidInfo;
      return androidDeviceInfo.androidId; // unique ID on Android
    }
  }
}
bool ParseBool(obj){
  return obj.toString().toLowerCase()=="true" || obj.toString()=="1";
}


class UserOperations{
  static Future<void> addCategory(String name, String color, bool productive, {bool bulk = false}) async{
    Map<String,String> queryBody= <String,String>{
      'username': username,
      'device_id': await Settings.UUID() ?? 'n/a',
      'name' : name,
      'color':color,
      'productive': productive ? '1':'0'
    };
    //Add Query
    Map<String,Object> query = {
      Queries.colLink: 'add_category',
      Queries.colData: jsonEncode(queryBody)
    };

    print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");

    await cacheDb.insert('Queries', query);

      //update Cache
      Map<String,Object> data = {
        Category.colCatId: username+name,
        Category.colName: name,
        Category.colColor: color,
        Category.colProductive: productive
      };
      await cacheDb.insert('Categories', data);

      await GetCategories(true);
    if(!bulk){
      //Add to server and refresh Cache
      await executeQueries();
    }
  }

  static Future<void> addTaskType(String name, String category, {bool bulk = false}) async{
    Map<String,String> queryBody= <String,String>{
      'id':username+name,
      'username': username,
      'device_id': await Settings.UUID() ?? 'n/a',
      'name' : name,
      'category': username + category
    };
    //Add Query
    Map<String,Object> query = {
      Queries.colLink: 'add_taskType',
      Queries.colData: jsonEncode(queryBody)
    };

    print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");

    await cacheDb.insert('Queries', query);

    //update Cache
    Map<String,Object> data = {
      TaskType.colId: username+name,
      Category.colName: name,
      Category.colCatId: username + category
    };
    await cacheDb.insert('TaskTypes', data);
    await GetTaskTypes(true);
    if(!bulk){
      //Add to server and refresh Cache
      await executeQueries();
    }
  }

  static Future<void> deleteTask(String name,{bulk=false}) async{
    Map<String,String> queryBody= <String,String>{
      'id':username+name,
      'username': username,
      'device_id': await Settings.UUID() ?? 'n/a',
    };
    //Add Query
    Map<String,Object> query = {
      Queries.colLink: 'delete_taskType',
      Queries.colData: jsonEncode(queryBody)
    };

    print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");

    await cacheDb.insert('Queries', query);

    //update Cache
    Map<String,Object> data = {
      TaskType.colId: username+name,
      Category.colName: name,
    };
    await cacheDb.rawDelete("DELETE FROM TaskTypes WHERE id='${username+name}'");
    await GetTaskTypes(true);
      //Add to server and refresh Cache

    if(!bulk) {
      await executeQueries();
    }
  }

  static Future<void> deleteCategory(String name,{bulk=false}) async{
    Map<String,String> queryBody= <String,String>{
      'id':username+name,
      'username': username,
      'device_id': await Settings.UUID() ?? 'n/a',
    };
    //Add Query
    Map<String,Object> query = {
      Queries.colLink: 'delete_category',
      Queries.colData: jsonEncode(queryBody)
    };

    print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}");

    await cacheDb.insert('Queries', query);

    //update Cache
    Map<String,Object> data = {
      TaskType.colId: username+name,
      Category.colName: name,
    };
    await cacheDb.rawDelete("DELETE FROM Categories WHERE ${Category.colCatId}='${username+name}'");
    await GetCategories(true);
    //Add to server and refresh Cache

    if(!bulk) {
      await executeQueries();
    }
  }


  static Future<void> executeQueries() async{
    if(offline){
      print("Cannot executre queries, Offline!");
      return;
    }

    List<Map<String,Object?>> queries = await cacheDb.query('Queries');

    for(Map<String,Object?> element in queries){
      int id = int.parse(element['id'].toString());
      String? file = element[Queries.colLink].toString();
      String? data = element[Queries.colData].toString();
      if(file==null || data==null){
        print("Null query, Ignoring...");
        continue;
      }
      print("Query[\n file:$file, \ndata:$data]");

      //Execute the http here
      Map<String, dynamic> body = jsonDecode(data);
      try {
        http.Response queryResponse = (await http.post(
            Uri.parse('http://161.97.127.136/task_tracker/$file.php'),
            body: body));
        print("Query executed : Results{${queryResponse.body}");
        if (queryResponse.body.toLowerCase().contains("success")) {
          await cacheDb.rawDelete('DELETE FROM Queries WHERE id=$id');
        }
        offline=false;
      }catch(e){
        offline=true;
      }
    }
  }
}

