import csv
import datetime
import glob
import io
import json
import os
import shutil
import uuid
import zipfile
from pathlib import Path
from typing import Any, Optional, Dict, List, Set

import config
from helpers.custom_logging import FlashLog
from loguru import logger
from helpers.file_root import relative_path  # don't remove this import - other parts of code depend on it


def save_file(text: str, filepath: str) -> None:
    with open(filepath, "w", encoding="utf8") as f:
        f.write(text)


def json_dump(obj: Any, filepath: str) -> None:
    with open(filepath, "w", encoding="utf8") as f:
        json.dump(obj, f, indent=4, ensure_ascii=False, sort_keys=True)


# Taken directly from Flask documentation
def is_whitelisted_extension(filename: str, whitelisted_extension: Set[str] = config.ALLOWED_SCANNER_EXTENSIONS):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in whitelisted_extension


def json_safe_load(filepath: str) -> Optional[Dict[str, Any]]:
    if not os.path.isfile(filepath):
        # logger.debug(f"File '{filepath}' does not exist.")
        return None

    with open(filepath, encoding="utf8") as f:
        try:
            return json.load(f)
        except json.decoder.JSONDecodeError as e:
            FlashLog.warning(f"Parsing of JSON file '{filepath}' failed. Error: {e}")

    return None


def extract_fid_from_filename(filename: str) -> str:
    return os.path.splitext(filename)[0]


def list_fids_in_paths(wildcard_path: List[str]) -> List[str]:
    fids = []
    for single_path in wildcard_path:
        full_paths_of_files = glob.glob(single_path)
        filenames = [os.path.basename(x) for x in full_paths_of_files]
        fids += [extract_fid_from_filename(x) for x in filenames]
    return list(set(fids))


def zip_folder_non_recursive_to_virtual(folder_path: str) -> io.BytesIO:
    return zip_multiple_folders_non_recursive_to_virtual([folder_path])


def zip_multiple_folders_non_recursive_to_virtual(folder_paths: List[str]) -> io.BytesIO:
    # note: this is not recursive

    # based on https://python.tutorialink.com/how-do-i-zip-an-entire-folder-with-subfolders-and-serve-it-through-flask-without-saving-anything-to-disk/
    memory_file = io.BytesIO()

    with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for folder_path in folder_paths:
            for root, _, files in os.walk(folder_path):
                folder_name = os.path.basename(os.path.dirname(root))
                for file in files:
                    zipf.write(os.path.join(root, file), os.path.join(folder_name, file))

    memory_file.seek(0)
    return memory_file


def zip_folder_recursively(folder_path: str) -> str:
    output_folder = generate_random_folder_path("zip")
    output_filename = os.path.join(output_folder, "output")  # extension is added inside shutil.make_archive
    shutil.make_archive(output_filename, "zip", folder_path)
    return output_filename+".zip"


def zip_folder_recursively_to_virtual(folder_path: str) -> io.BytesIO:
    output_filename = zip_folder_recursively(folder_path)
    with open(output_filename, "rb") as f:
        return io.BytesIO(f.read())


def generate_random_folder_name(label: str = "") -> str:
    iso8601_time: str = datetime.datetime.now().replace(microsecond=0).isoformat()
    prefix = iso8601_time.replace(":", "-")
    suffix = str(uuid.uuid4())[:8]
    return f"{prefix}_{label}_{suffix}"


def generate_random_folder_path(label: str = "") -> str:
    return folder_in_upload_path(generate_random_folder_name(label), exists_ok=False)


def folder_in_upload_path(folder_name: str = "", exists_ok: bool = True) -> str:
    r_path = os.path.join(config.FLASK_UPLOAD_FOLDER, folder_name)
    Path(r_path).mkdir(parents=True, exist_ok=exists_ok)
    return r_path


def persistent_audit_scopes_file_path(audit_id: str) -> str:
    folder_path = folder_in_upload_path(f"audit_{audit_id}")
    file_path = os.path.join(folder_path, "services.json")
    return file_path


def json_list_to_csv(data: List[Dict]) -> str:
    if len(data) == 0:
        return ''

    output = io.StringIO()
    writer = csv.writer(output, quoting=csv.QUOTE_ALL)

    writer.writerow(data[0].keys())  # header

    for single_finding in data:
        writer.writerow(single_finding.values())

    return output.getvalue()
