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

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:html/parser.dart';
import 'package:path_provider/path_provider.dart';
import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/api/wikimedia.dart';

/*
        Voyage Handbook - The open-source WikiVoyage reader
        Copyright (C) 2023 Matyáš Caras

        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License version 3 as published by
        the Free Software Foundation.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

/// Used to ease up accessing local files
class StorageAccess {
  /// Get files in `recent` folder, which contains recently opened pages
  static Future<List<Map<String, dynamic>>> get recent async {
    var files =
        Directory("${(await getApplicationDocumentsDirectory()).path}/recent");
    if (!files.existsSync()) files.createSync();
    return files
        .listSync()
        .whereType<File>()
        .toList()
        .map<Map<String, dynamic>>((e) => jsonDecode(e.readAsStringSync()))
        .toList();
  }

  /// Get files in `offline` folder, which contains recently opened pages
  static Future<List<Map<String, dynamic>>> get offline async {
    var files =
        Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
    if (!files.existsSync()) files.createSync();
    return files
        .listSync()
        .whereType<File>()
        .toList()
        .map<Map<String, dynamic>>((e) => jsonDecode(e.readAsStringSync()))
        .toList();
  }

  static void addToRecents(String pageName, String pageKey) async {
    var files =
        Directory("${(await getApplicationDocumentsDirectory()).path}/recent");
    if (!files.existsSync()) files.createSync();
    var content = files.listSync();
    if (content.length > 4) {
      // delete last recent
      // TODO: configurable
      File? f;
      for (var file in content) {
        if (file is Directory) continue;
        var modi = (await file.stat()).modified;
        if (f == null || (await f.stat()).modified.isAfter(modi)) {
          f = file as File;
        }
      }
      f!.deleteSync();
    }

    var recent = File("${files.path}/${pageName.replaceAll(' ', '_')}");
    if (recent.existsSync()) {
      // if recent already exists, simply change date
      var recentContent = jsonDecode(recent.readAsStringSync());
      recentContent["date"] = DateTime.now().millisecondsSinceEpoch;
      recent.writeAsStringSync(jsonEncode(recentContent));
    } else {
      // else create files
      var recentContent = {
        "date": DateTime.now().millisecondsSinceEpoch,
        "name": pageName,
        "key": pageKey
      };
      recent.writeAsStringSync(jsonEncode(recentContent));
    }
  }

  static Future<bool> isDownloaded(String pageKey) async {
    var files =
        Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
    if (!files.existsSync()) files.createSync();
    var offlinePage = File("${files.path}/$pageKey");
    return offlinePage.existsSync();
  }

  static Future<void> downloadArticle(String pageKey, String pageTitle) async {
    try {
      var files = Directory(
          "${(await getApplicationDocumentsDirectory()).path}/offline");
      if (!files.existsSync()) files.createSync();
      var offlinePage = File("${files.path}/$pageKey");
      var raw = await WikiApi.getRawPage(pageKey);
      var page = parse(raw.html);
      var out = "<html><head></head><body>";
      var sections = page.body!.children
          .where((element) => element.localName == "section");
      for (var el in sections) {
        out += el.outerHtml;
      }
      out += "</body></html>";
      var imgMatch = RegExp(r'<img.+?src="(.+?)".+?>')
          .allMatches(page.body!.innerHtml); // TODO: ask to overwrite
      if (imgMatch.isNotEmpty) {
        // download images offline
        for (var match in imgMatch) {
          var src = match.group(1)!;
          if (!src.startsWith("https://")) {
            src = src.replaceAll("//", "https://");
          }
          var r = await Dio().get(
            src,
            options: Options(
              responseType: ResponseType.bytes,
              followRedirects: false,
              validateStatus: (status) {
                return (status ?? 200) < 500;
              },
            ),
          );
          var assetDir = Directory("${files.path}/assets");
          if (!assetDir.existsSync()) assetDir.createSync();
          var img = File(
              "${assetDir.path}/${src.split('/').last.replaceAll(RegExp(r"(?!\..+?)\?.+"), "")}");
          print(img.path);
          var openImg = img.openSync(mode: FileMode.write);
          openImg.writeFromSync(r.data);
        }
      }
      raw.html = out;
      if (sections.isEmpty) {
        return Future.error("No sections to save");
      }
      offlinePage.writeAsStringSync(jsonEncode(raw.toJson()),
          mode: FileMode.writeOnly);
    } catch (e) {
      if (kDebugMode) {
        print(e);
      }
      return Future.error(e);
    }
  }

  static Future<RawPage?> getOfflinePage(String pageKey) async {
    var files =
        Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
    if (!files.existsSync()) return null;
    var offlinePage = File("${files.path}/$pageKey");
    if (!offlinePage.existsSync()) return null;
    try {
      return RawPage.fromJson(jsonDecode(offlinePage.readAsStringSync()));
    } catch (e) {
      if (kDebugMode) {
        print(e);
      }
      return null;
    }
  }

  static Future<File?> getOfflineImage(String key) async {
    var files = Directory(
        "${(await getApplicationDocumentsDirectory()).path}/offline/assets");
    return File("${files.path}/$key");
  }
}
