//import 'package:html';

import 'dart:convert';
import 'dart:developer';
import 'dart:io';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:http/http.dart' as http;

import 'package:flutter/foundation.dart';
import 'package:jiffy/jiffy.dart';
import 'package:tp5/core/utils.dart';

final lidoapiProvider = Provider<LidoApi>((ref) {
  return LidoApi();
});

class LidoApi {
  //static const url = "https://tar.lido.aero";
  static const url =
      kIsWeb ? "https://proxy.fares.cyou" : "https://tar.lido.aero";
  //static const url = "https://proxy.fares.cyou";
  static const realurl = "https://tar.lido.aero";

  final timeout = Duration(seconds: 7);
  LidoApi({this.username = "", this.password = ""});
  bool logged = false;
  String username;
  String password;
  String get cookie =>
      "${cookieMap.keys.fold("", (t, e) => (t == "") ? "$e=${cookieMap[e]}" : "$t; $e=${cookieMap[e]}")};SameSite=None; Secure";

  Map cookieMap = {};
  Map<String, String> get headers => {
        "Accept": "application/vnd.lsy.lido.lcb.v1.hal+json",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "en",
        "Connection": "keep-alive",
        "Content-Type": "application/json;charset=UTF-8",
        //"Host": Uri.parse(realurl).host,
        "Origin": Uri.parse(realurl).host,
        //"sec-ch-ua":'" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"',
        // "sec-ch-ua-mobile": "?0",
        // "sec-ch-ua-platform": "Windows",
        // "Sec-Fetch-Dest": "empty",
        // "Sec-Fetch-Mode": "cors",
        // "Sec-Fetch-Site": "same-origin",
        "User-Agent":
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36',
        "X-lido-applicationId": "lido-lcb",
        "X-lido-clientId": "lido-lcb-web",
        "X-lido-customerId": "TAR",
        "X-lido-operatingAirline": "",
        "X-lido-timeStamp": DateTime.now().toUtc().toIso8601String(),
        "X-lido-traceId": "0c497133-2a92-45a0-8ffe-a03c77852201",
        "cookie": "SERVERID=docker2; $cookie",
        "X-lido-csrf": cookieMap["lido_csrf"] ?? "",
      };
  Future logout() async {
    var response = await http.post(Uri.parse('${url}USR/actionLogout.jsp'),
        body: {"Cookie": cookie});
    logged = false;
  }

  bool credSaved() {
    return (Hive.box("settings").get("lido_user", defaultValue: "") != "") &&
        (Hive.box("settings").get("lido_pass", defaultValue: "") != "");
  }

  Future login({String? user, String? pass, String? newpass}) async {
    username = user ?? Hive.box("settings").get("lido_user", defaultValue: "");
    password = pass ?? Hive.box("settings").get("lido_pass", defaultValue: "");
    try {
      var response = await http
          .get(
            Uri.parse(
                '$url/lido/las/login.jsp?DESMON_RESULT_PAGE=$realurl/briefing//'),
          )
          .timeout(timeout);
      cookieMap.addAll(_getcookies(response.headers["set-cookie"] ??
          response.headers["_set-cookie"] ??
          ""));
      //print(response.headers);
      response = await http.post(
          Uri.parse('$url/lido/las/dwr/call/plaincall/LoginBean.login.dwr'),
          body: {
            "callCount": "1",
            "nextReverseAjaxIndex": "0",
            "c0-scriptName": "LoginBean",
            "c0-methodName": "login",
            "c0-id": "0",
            "c0-param0": "string:$username",
            "c0-param1": "string:$password",
            "c0-param2": "string:${newpass ?? ""}",
            "c0-param3": "string:LIDO",
            "c0-param4": "string:en",
            "batchId": "0",
            "instanceId": "0",
            "page":
                "/lido/las/login.jsp?DESMON_RESULT_PAGE=$realurl/briefing&DESMON_CODE=LAS_002&DESMON_LANG=en",
            "scriptSessionId":
                "lSUqDnaTyhevHY6vpNvPdIR~IuUygzP8a2p/kYr1m2p-BgwpWJ4*a"
          },
          headers: {});
      cookieMap.addAll(_getcookies(response.headers["set-cookie"] ?? ""));
      //log("lidoapi: login : $username $password ${response.statusCode} ${response.body}");

      final message = RegExp(r'(?<=errorMessage2:")(.+)(?=",)')
              .firstMatch(response.body)
              ?.group(0) ??
          RegExp(r'(?<=errorMessage1:")(.+)(?=",)')
              .firstMatch(response.body)
              ?.group(0);
      final errorcode = RegExp(r'(?<=errorCode:)(.+?)(?=,)')
              .firstMatch(response.body)
              ?.group(0) ??
          "xx";

      final plschangepass =
          response.body.contains("please change your password");

      final changepass = RegExp(r'(?<=forcePasswdChange:)(.+)(?=,)')
              .firstMatch(response.body)
              ?.group(0) ==
          "true";

      // print("lidoapi: login: errorcode: $errorcode");
      // print("lidoapi: login: message: $message");
      // print("lidoapi: login: changepass: $changepass");
      // print("lidoapi: login: responsebody: ${response.body}");

      logged = errorcode == "null";

      cookieMap = (!logged) ? {} : cookieMap;
      // log("api lido 4d login \nuser:$username\npass:$password\nstatuscode${response.statusCode}\nlogged:$logged\nmsg:$message");
      log("api lido 4d login errorcode: $errorcode");
      //invalide username or password
      if (['"LAS_112"', '"LAS_113"'].contains(errorcode)) {
        if (user != null) {
          print("lidoapi: login: invalidate user & pass");
          await Hive.box("settings").delete("lido_user");
          await Hive.box("settings").delete("lido_pass");
        }
        //username = "";
        password = "";
      }
      final res = extractCallbackData(response.body);
      final passwordChanged =
          (newpass != null) && (res["errorMessage1"] != null);

      // log("lidoapi: login: return: ${extractCallbackData(response.body)}");
      if (newpass != null && passwordChanged) {
        await Hive.box("settings").put("lido_user", username);
        await Hive.box("settings").put("lido_pass", newpass);
        log("lidoapi: login: newpass: $newpass");
      } else if (logged && newpass == null) {
        await Hive.box("settings").put("lido_user", username);
        await Hive.box("settings").put("lido_pass", password);
      }
      return {
        'error': logged ? null : message,
        'data': {
          "logged": logged,
          "message": message,
          "passwordChanged": passwordChanged
        },
        'meta': {
          "changepass": changepass,
          "plschangepass": plschangepass,
          "user": username,
          "pass": password,
          "cookie": cookieMap,
          "errorcode": errorcode
        }
      };
    } catch (e) {
      return {
        'error': e.toString(),
        'data': {"logged": false},
        'meta': {"user": username, "pass": password, "cookie": null}
      };
    }
  }

  Future forgotpass({String? user}) async {
    var response = await http.get(Uri.parse('$url/lido/las/login.jsp'));
    cookieMap.addAll(_getcookies(response.headers["set-cookie"] ?? ""));
    response = await http.post(
        Uri.parse(
            '$url/lido/las/dwr/call/plaincall/LoginBean.forgotPassword.dwr'),
        body: {
          "callCount": "1",
          "nextReverseAjaxIndex": "0",
          "c0-scriptName": "LoginBean",
          "c0-methodName": "forgotPasswordLink",
          "c0-id": "0",
          "c0-param0": "string:$user",
          "c0-param1":
              "string:$realurl/lido/lsc/application-center-ui/startCenter",
          "c0-param2": "string:LIDO",
          "c0-param3": "string:en",
          "batchId": "0",
          "instanceId": "0",
          "page":
              "/lido/las/forgotPassword.jsp?DESMON_RESULT_PAGE=$realurl/briefing&DESMON_CODE=LAS_002&DESMON_LANG=en",
          "scriptSessionId":
              "lSUqDnaTyhevHY6vpNvPdIR~IuUygzP8a2p/kYr1m2p-BgwpWJ4*a"
        });
    cookieMap.addAll(_getcookies(response.headers["set-cookie"] ?? ""));
    //print("lidoapi: forgotpass: header:  ${response.headers}");
    //print("lidoapi: forgotpass: ${response.body}");
    return {
      'error':
          response.statusCode < 300 ? null : response.statusCode.toString(),
      'data': {},
      'meta': {
        "user": username,
        "pass": password,
        "cookie": cookieMap,
      }
    };
  }

  Future changePass(
      {required String user,
      required String pass,
      required String token}) async {
    var response = await http.get(Uri.parse('$url/lido/las/login.jsp'));
    cookieMap.addAll(_getcookies(response.headers["set-cookie"] ?? ""));
    response = await http.post(
        Uri.parse(
            '$url/lido/las/dwr/call/plaincall/LoginBean.forgotPassword.dwr'),
        body: {
          "callCount": "1",
          "nextReverseAjaxIndex": "0",
          "c0-scriptName": "LoginBean",
          "c0-methodName": "changePassword",
          "c0-id": "0",
          "c0-param0": "string:$user",
          "c0-param1": "string:$pass",
          "c0-param2": "string:$token",
          "batchId": "0",
          "instanceId": "0",
          "page": "/lido/las/changePassword.jsp?token=$token",
          "scriptSessionId":
              "lBgkwVDs5bVASGOoKyVfCkOJjxBwXBJERcp/4WXGUcp-7few1oOHw"
        });
    cookieMap.addAll(_getcookies(response.headers["set-cookie"] ?? ""));
    final decodedMsg = extractCallbackData(response.body);
    for (var e in decodedMsg.entries) {
      decodedMsg[e.key] = e.value == "null" ? null : e.value;
    }
    return {
      'error':
          response.statusCode < 300 ? null : response.statusCode.toString(),
      'data': {
        "error": decodedMsg["erroeMessage1"] ?? decodedMsg["erroeMessage2"]
      },
      'meta': {
        "user": username,
        "pass": password,
        "cookie": cookieMap,
      }
    };
  }

  Future getDoc(String link,
      {String? mediaType, bool loginfirst = false}) async {
    try {
      if (loginfirst) {
        final out = await login();
        if (!logged) return out;
      }

      final murl = link.replaceFirst(realurl, url);
      var response = await http.get(
          Uri.parse(murl)
          // .replace(queryParameters: {}),
          ,
          headers: {"X-lido-businessId": "ViewBP", ...headers});
      // log(response.headers.toString());
      Map decodedresponse;

      print("lidoapi: getdoc: ${response.statusCode} ");
      // try {
      //   decodedresponse = json.decode(response.body) ?? {};
      // } catch (e) {
      //   decodedresponse = {
      //     "body": response.body,
      //     "response": response.statusCode
      //   };
      // }
      //log("lidoapi: getDoc: responsestatus: ${response.body}");
      if (!loginfirst && response.statusCode != 200) {
        return await getDoc(link, mediaType: mediaType, loginfirst: true);
      }
      // String? message = decodedresponse["errorsAndWarnings"]?["warnings"]?[0]
      //         ?["message"] ??
      //     decodedresponse["errorsAndWarnings"]?["errors"]?[0]?["message"] ??
      //     ((response.statusCode == 200) ? null : "ERR");

      // log("lidoapi: getDoc: success: ${decodedresponse["content"]}");

      return {
        'error': response.statusCode >= 300 ? response.statusCode : null,
        'data': response.body,
        'meta': {"user": username, "pass": password, "cookie": cookieMap}
      };
    } catch (e) {
      return {'error': e.toString(), 'data': {}, 'meta': {}};
    }
  }

  Future list(
      {Jiffy? start,
      Jiffy? end,
      String dep = "",
      String des = "",
      String al = "",
      String fnum = "",
      bool loginfirst = false}) async {
    final key =
        "${start?.format(pattern: "yyyyMMdd_HH_mm") ?? ""} $dep $des $al $fnum";

    try {
      if (loginfirst) {
        final out = await login();
        if (!logged) return out;
      }

      final startdate = (start ?? (Jiffy.now().toUtc().subtract(hours: 12)))
          .millisecondsSinceEpoch;
      final enddate = (end ??
              Jiffy.parseFromMillisecondsSinceEpoch(startdate).add(hours: 48))
          .millisecondsSinceEpoch;
      var response = await http.get(
          Uri.parse(
                  "$url/lido/lcb/ui/flights?page=0&size=100&startDateTime=1653148128544&endDateTime=1653191328544")
              .replace(queryParameters: {
            "page": "0",
            "size": "1000",
            "startDateTime": startdate.toString(),
            "endDateTime": enddate.toString(),
            ...(al != "" ? {"commAirLine": al} : {}),
            ...(fnum != "" ? {"flightNumber": fnum} : {}),
            ...(dep != "" ? {"departure": dep} : {}),
            ...(des != "" ? {"destination": des} : {}),
          }),
          headers: {"X-lido-businessId": "SearchFlights", ...headers});
      //log(response.headers.toString());
      Map decodedresponse;

      // print("${response.body}");
      try {
        decodedresponse = json.decode(response.body) ?? {};
      } catch (e) {
        decodedresponse = {
          "body": response.body,
          "response": response.statusCode
        };
      }
      //log("lidoapi: list: responsestatus: ${response.statusCode}");
      if (!loginfirst && response.statusCode != 200) {
        return await list(
            al: al,
            fnum: fnum,
            dep: dep,
            des: des,
            start: start,
            end: end,
            loginfirst: true);
      }
      String? message = decodedresponse["errorsAndWarnings"]?["warnings"]?[0]
              ?["message"] ??
          decodedresponse["errorsAndWarnings"]?["errors"]?[0]?["message"] ??
          ((response.statusCode == 200) ? null : "ERR");

      // log("lidoapi: list: success: ${decodedresponse["content"]}");
      Map res = {
        'error': message,
        'data': (message == null) ? decodedresponse["content"] : null,
        'meta': {
          "legs": (decodedresponse["totalElements"] ?? 0) > 0
              ? decodedresponse["content"].fold([], (t, e) {
                  t.add(e["leg"]["legidentifier"]);
                  return t;
                })
              : [],
          "user": username,
          "pass": password,
          "nb": decodedresponse["totalElements"] ?? 0,
          "cookie": cookieMap
        }
      };

      // cache response if success
      Hive.box("lido").put(key, json.encode(res));

      return res;
    } catch (e) {
      final res = {
        'error': e.toString(),
        'data': {},
        'meta': {},
        'cache': json.decode(Hive.box("lido").get(key, defaultValue: "{}"))
      };
      return res;
    }
  }

  Future ofp(
      {required String legid,
      String? brieflink,
      bool loginfirst = false}) async {
    try {
      if (loginfirst) {
        final out = await login();
        if (!logged) return out;
      }

      // brieflink = res["data"]?[0]?["_links"]?["self"]?["href"];
      // legid = res["data"]?[0]?["leg"]?["legidentifier"];

      brieflink = brieflink ?? "$url/lido/lcb/ui/$legid/briefing";
      var response = await http.put(Uri.parse(brieflink),
          headers: {
            "X-lido-businessId": 'SearchBP',
            ...headers,
          },
          //body: json.encode(({"commercialAirline": al,"flightNumber": fnum,"operationalSuffix": "","date": dateoforigin.toString()

          body: json.encode(({
            "categories": [
              "OFP",
              "IWFR",
              "ATS",
              "DISP",
              "NOTAM",
              "SIGWXROUTE",
              "VERTPROF",
              "CREWINFO",
              "UAD",
              "RAIM",
              "VAATCA",
              "CDA",
              "DISMAP",
              "DMS",
              "ABD",
              "OFPDLK",
              "TLR",

              // "OFP",
              // "NOTAM",
              // // "APTDXML",
              // "SOFP",
              // "CDA",
              // "RADAR",
              // "OFPDLK",
              // // "ATSXML",
              // // "REGWXXML",
              // "WXSIGWX",
              // "SIGWXROUTE",
              // "VAATCA",
              // // "ASPDXML",
              // // "NOTAMXML",
              // "OTS",
              // "SFC",
              // "WINDDATA",
              // // "RAIMXML",
              // "TLR",
              // // "ARPTWXXML",
              // "SOFPINFO",
              // // "HAZARDADVISORYXML",
              // "IWFRXML",
              // "OFPNLXML",
              // "VERTPROF",
              // "SOFPSUM",
              // "APLI",
              // "RAIM",
              // "UAD",
              // "DISMAP",
              // "WXSATWX"

              // "APLI",
              // "APTDXML",
              // "SOFP",
              // "CDA",
              // "RADAR",
              // "OFPDLK",
              // "ATSXML",
              // "REGWXXML",
              // "WXSIGWX",
              // "SIGWXROUTE",
              // "VAATCA",
              // "ASPDXML",
              // "NOTAMXML",
              // "OTS",
              // "SFC",
              // "WINDDATA",
              // "RAIMXML",
              // "ARPTWXXML",
              // "SOFPINFO",
              // "IWFRXML",
              // "OFPNLXML",
              // "VERTPROF",
              // "SOFPSUM",
              // "RAIM",
              // "UAD",
              // "DISMAP",
              // "WXSATWX"
            ],
          })));
      //log(response.body);

      Map decodedresponse;
      try {
        decodedresponse = (json.decode(response.body) ?? {});
      } catch (e) {
        decodedresponse = {};
      }
      String? message = decodedresponse["errorsAndWarnings"]?["warnings"]?[0]
              ?["message"] ??
          decodedresponse["errorsAndWarnings"]?["errors"]?[0]?["message"] ??
          ((response.statusCode == 201) ? null : "ERR");

      log("lidoapi: ofp: statuscode: ${response.statusCode} msg: $message");

      return {
        'error': (message == null) ? null : message,
        'data': decodedresponse,
        'meta': {
          "user": username,
          "pass": password,
          "cookie": cookieMap,
          "legid": legid
        }
      };
    } catch (e) {
      return {'error': e.toString(), 'data': {}, 'meta': {}};
    }
  }

  Future ofppdf(
      {required String legid, String? fileid, bool loginfirst = false}) async {
    try {
      if (loginfirst) {
        final out = await login();
        if (!logged) return out;
      }

      var response = await http.put(
          Uri.parse("$url/lido/lcb/ui/briefing/multi/print?force=true"),
          headers: {
            "Accept":
                "application/vnd.lsy.lido.lcb.v1.hal+json,application/pdf",
            //"Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "en",
            "Connection": "keep-alive",
            "Content-Type": "application/json;charset=UTF-8",
            "Cookie": cookie,
            //"host": Uri.parse(realurl).host,
            "Origin": Uri.parse(realurl).host,
            // "sec-ch-ua":'" Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"',

            "User-Agent":
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36",
            "X-lido-applicationId": "lido-lcb",
            "X-lido-businessId": "printBP",
            "X-lido-clientId": "lido-lcb-web",
            "X-lido-csrf": cookieMap["lido_csrf"] ?? "",
            "X-lido-customerId": "TAR",
            "X-lido-operatingAirline": "TU",
            "X-lido-timeStamp": "2022-05-25T06:29:56.659Z",
            "X-lido-traceId": "3cbab7e0-56ca-49ec-b158-8899c0d3bbbb",
          },
          body: json.encode({
            "legIdentifiers": [legid],
            "categories": [
              "OFP",
              "ATS",
              "CREWINFO",
              "DISP",
              "IWFR",
              "NOTAM",
              "UAD",
              "WXSIGWX",
              "VERTPROF",
              "RAIM",
              "DMS",
              "WXNOTAM",
              "ABD",
              "SIGWXROUTE",
              //"WXSATWX"
            ],
            "merge": false,
            "quickprint": false,
            "weatherDataRequired": false,
          }));

      if (!loginfirst && response.statusCode > 201) {
        return ofppdf(legid: legid, fileid: fileid, loginfirst: true);
      }

      // log("lidoapi: pdfofp2: status: ${response.statusCode} fileid: $fileid ");
      //final id ="${DateFormat("yyyyMMdd").format(DateTime.fromMillisecondsSinceEpoch(dateoforigin))}_${dep}_${des}_$al$fnum";
      final ok = response.headers.containsKey("content-disposition");
      // log("lidoapi: pdfofp2: status: ${response.headers} ");

      final xfileid = fileid ?? PathTo().lidoFile("$legid.pdf");

      if (ok) {
        await downloadPdf(response, xfileid);
      }
      final res = {
        'error': (ok) ? null : "no PDF downloaded",
        'data': (ok)
            ? {
                "downloaded": true,
                "fileid": xfileid,
              }
            : {
                "downloaded": false,
                "fileid": null,
              },
        'meta': {"user": username, "pass": password, "cookie": cookieMap}
      };

      return res;
    } catch (e) {
      final res = {
        'error': e.toString(),
        'data': {
          {
            "downloaded": false,
            "fileid": null,
          }
        },
        'meta': {}
      };

      final xfileid = fileid ?? PathTo().lidoFile("$legid.pdf");
      if (File(xfileid).existsSync()) {
        res["cache"] = xfileid;
      }

      return res;
    }
  }

  Map<String, String> _getcookies(String cookiesData,
      {List<String> vars = const [
        "lido_auth",
        "lido_csrf",
        "JSESSIONID",
        "SERVERID"
      ]}) {
    Map<String, String> cookies = {};
    for (var title in vars) {
      if (cookiesData.contains(title)) {
        List at =
            (RegExp(r'(' + title + ')(.*?)[^;]+').stringMatch(cookiesData) ??
                    "")
                .split('=');
        cookies[title] = at[1];
      }
    }
    return cookies;
  }

  Future<void> downloadPdf(http.Response response, String fileid) async {
    try {
      if (response.statusCode == 201) {
        final pdfBytes = response.bodyBytes;
        final file = File(fileid);
        await file.writeAsBytes(pdfBytes);
        print('PDF downloaded and saved to $fileid');
      } else {
        print('Error downloading PDF: ${response.statusCode}');
      }
    } catch (e) {
      print('Error downloading file: $e');
    }
  }

  Map<String, String?> extractCallbackData(String inputString) {
    // log("lidoapi: extractCallbackData: $inputString");
    final RegExp regex =
        RegExp(r'dwr\.engine\.remote\.handleCallback\(\"0\",\"0\",([^)]+)\)');
    final Match? match = regex.firstMatch(inputString);
    //log("lidoapi: extractCallbackData: match: ${match?[1]}");
    if (match != null && match[1] != null) {
      final String callbackData = match[1]!;
      final Map<String, String?> jsonData = convertStringToMap(callbackData);
      //log("lidoapi: extractCallbackData: jsonData: $jsonData");
      return jsonData;
    }
    return {};
  }

  Map<String, String?> convertStringToMap(String inputString) {
    final Map<String, String?> result = {};
    final RegExp regex = RegExp(r'(\w+):("?.*"?)(,|})');
    final Iterable<RegExpMatch> matches = regex.allMatches(inputString);
    for (var match in matches) {
      final String key = match[1]!;
      final String value = match[2]!;
      result[key] = value;
    }
    return result;
  }
}
