from datetime import date
from pathlib import Path
from typing import Tuple
import hashlib

from . import filesystem, utils

import logging
import yaml
import uuid

def read_metadata(file_path) -> dict:
    has_yaml = has_yaml_block(file_path)
    try:
        if has_yaml:
            with open(file_path) as file:
                # skip likes till yaml block is found
                for line in file:
                    if line.startswith("---"):
                        break
                yaml_lines = []
                # read all strings until next "---
                for line in file:
                    if line.startswith("---"):
                        break
                    else:
                        yaml_lines.append(line)
                    yaml_text = "".join(yaml_lines)
                yaml_info = yaml.load(yaml_text, Loader=yaml.SafeLoader)
                return yaml_info
        else:
            return {}
    except:
        return {}


def add_missing_metadata_variables(metadata) -> Tuple[dict, bool]:
    are_variables_added = False
    string_fields = [
        "description",
        "last_modified",
        "sha256",
        "title",
        "uuid",
        "category",
        "website",
    ]
    list_fields = ["tags", "modification_record"]
    boolean_fields = ["publish"]

    for field in string_fields:
        if field not in metadata.keys():
            metadata[field] = ""
            are_variables_added = True

    for field in list_fields:
        if field not in metadata.keys():
            metadata[field] = []
            are_variables_added = True

    for field in boolean_fields:
        if field not in metadata.keys():
            metadata[field] = False

    return metadata, are_variables_added


def calculate_sha256(data) -> str:
    return hashlib.sha256(data.encode()).hexdigest()


def has_yaml_block(file_path) -> bool:
    separator_count = 0
    # run 1
    # check if file has yaml
    # file has yaml if there are 2 seperators
    with open(file_path) as note:
        for line in note:
            if line.startswith("---"):
                separator_count = separator_count + 1
        for line in note:
            separator_count = separator_count + 1
    if separator_count >= 2:
        return True
    else:
        return False


def get_file_body(file_path) -> str:
    has_yaml = has_yaml_block(file_path)

    # get body
    if has_yaml:
        file_body = ""
        with open(file_path) as note:
            for line in note:
                if line.startswith("---"):
                    break
            for line in note:
                if line.startswith("---"):
                    break
            # remaining lines are markdown lines
            file_body = "".join(note)
        return file_body
    else:
        # there is no yaml block
        # so treat entire file as body
        file_body = ""
        with open(file_path) as note:
            # remaining lines are markdown lines
            file_body = "".join(note)
        return file_body


def update_file(file_path, metadata, body) -> None:
    file_data = "---\n" + yaml.dump(metadata) + "---\n" + body
    filesystem.write_file(path=file_path, text_data=file_data)


def stringify_metadata(metadata):
    # yaml has ambiguity
    # setting something as 123 which I intended as string is read as a number into python
    # this causes all sorts of problems later on when concatenating strings
    metadata["title"] = str(metadata["title"])
    metadata["category"] = str(metadata["category"])
    for i, tag in enumerate(metadata["tags"]):
        (metadata["tags"])[i] = str(tag)
    return metadata


def update_metadata_and_file(file_path) -> None:
    # set default value
    update_to_disk = False
    body = get_file_body(file_path)

    # step 1/5 read existing metadata
    yaml_metadata = read_metadata(file_path)
    # initialize empty dictionary if metadata dictionary is empty
    if not yaml_metadata:
        yaml_metadata = {}

    # step 2/5 if missing metadata, insert variables into yaml object
    yaml_metadata, are_variables_added = add_missing_metadata_variables(yaml_metadata)
    if are_variables_added:
        update_to_disk = True

    # metadata preprocessing

    # step 3/5 update medatata variables
    # add uuid if it's missing
    if not yaml_metadata["uuid"]:
        update_to_disk = True
        yaml_metadata["uuid"] = str(uuid.uuid4())
    # update sha256 and modification record
    # con't consider lat_modified, modification_record, sha256 since they are always updated
    # and cause circular loop. Just think about it.
    old_sha256 = yaml_metadata["sha256"]
    string_fields = [
        "description",
        "title",
        "uuid",
        "category",
        "website",
    ]
    list_fields = ["tags"]
    boolean_fields = ["publish"]

    metadata_string = ""
    for string_field in string_fields:
        metadata_string += str(yaml_metadata[string_field])

    for list_field in list_fields:
        entries_in_list = yaml_metadata[list_field]
        for entry in entries_in_list:
            metadata_string += entry

    for boolean_field in boolean_fields:
        metadata_string += str(yaml_metadata[boolean_field])

    sha_data = metadata_string + body
    new_sha256 = calculate_sha256(sha_data)

    # hashes don't match, update modification record
    if old_sha256 != new_sha256:
        yaml_metadata["sha256"] = new_sha256
        update_to_disk = True
        # update last modified date
        date_modified = filesystem.get_modified_date(file_path)
        yaml_metadata["last_modified"] = date_modified
        # update modification record
        if yaml_metadata["last_modified"] not in yaml_metadata["modification_record"]:
            yaml_metadata["modification_record"].append(date_modified)

    # step 4/5 rename file on disk if required
    if update_to_disk:
        update_file(file_path, yaml_metadata, body)

    # step 5/5 rename file if filename name is not of the proper format (category title uuid)
    has_filename_data = True
    if not yaml_metadata["category"]:
        has_filename_data = False
    if not yaml_metadata["title"]:
        has_filename_data = False
    if not yaml_metadata["uuid"]:
        has_filename_data = False
    if has_filename_data:
        # set filename to the proper format: category_title_uuid
        new_filename = utils.generate_alphanumeric_string(
            yaml_metadata["category"]
            + "_"
            + yaml_metadata["title"]
            + "_"
            + yaml_metadata["uuid"],
            file_path.suffix,
        )
        new_file_path = file_path.parents[0] / new_filename
        # rename if existing filename is not of the proper format
        if file_path.name != new_file_path.name:
            # check if exact same filename already exists in directory
            if filesystem.file_exists(new_file_path):
                # i have designed it such that this case never arises and exception is never hit
                # However if it happens, this exception is here to catch it and show me
                # So I can correct my logic and implementation
                raise Exception(
                    # Do not remove.
                    # This is a very serious error.
                    # It means that something has fallen through
                    #   the various logical checks in the steps before this.
                    f"the filename {new_file_path} is not unique. file with same name already exists"
                )
            else:
                filesystem.rename_file(
                    original_path=file_path, new_file_path=new_file_path
                )
    else:
        # no filename data
        # set filename to the format: old_filename_uuid
        # change filename if it does not have uuid in it
        if yaml_metadata["uuid"] not in file_path.name:
            new_filename = file_path.name + yaml_metadata["uuid"] + file_path.suffix
            new_file_path = file_path.parents[0] / new_filename
            # Rename file
            filesystem.rename_file(original_path=file_path, new_file_path=new_file_path)


def has_mandatory_metadata(file_path: Path) -> bool:
    """
    check if file has mandatory metadata fields:
    title
    category
    sha256
    tags
    """
    has_required_fields = True
    yaml_metadata = read_metadata(file_path)
    required_fields = ["title", "category", "sha256", "tags"]
    for field in required_fields:
        if not yaml_metadata[field]:
            return False
    return True


def move_note(note_path, destination_directory, replace):
    # move note
    filesystem.move_file(
        source=note_path, destination_directory=destination_directory, replace=replace
    )


def copy_note_to_directory(note_path, target_directory_path):

    filesystem.copy_file_to_directory(
        file_path=note_path, target_directory_path=target_directory_path
    )
