import hashlib
import json
import os
import subprocess
import sys
import uuid
from datetime import datetime
import xml.etree.ElementTree as ET

import metsrw
import metsrw_override

DELETE_METADATA_FILE = True


def gen_sf_file_data():
    sf_file_data = {}
    # Run sf using local signature file

    output = \
        subprocess.Popen(
            [f"{current_directory}/sf/sf.exe", "-sig", f"{current_directory}/sf/default.sig", "-json", sip_directory],
            stdout=subprocess.PIPE
        ).communicate()[0].decode("utf-8")

    # Converts <str>output to <dict>
    data = json.loads(output)
    # Gather only required data
    for file in data['files']:
        modified_date = datetime.strptime(file['modified'], "%Y-%m-%dT%H:%M:%S%z").strftime("%Y-%m-%dT%H:%M:%S")
        file_size = file['filesize']
        mime_type = file['matches'][0]['mime']
        sf_file_data[file['filename'].replace("\\", "/")] = {'filesize': file_size,
                                                             'modified': modified_date,
                                                             'mimetype': mime_type
                                                             }
    return sf_file_data


def get_metadata():
    """
        Finds metadata.xml file in the root folder and extracts data
        TODO: Then deletes metadata.xml
    """
    metadata_dict = {}
    if "/representations/" in sip_directory:
        metadata_file = f"{'/'.join(sip_directory.split('/')[:-2])}/metadata.xml"
    else:
        metadata_file = f"{sip_directory}/metadata.xml"
    if os.path.isfile(metadata_file):
        open_metadata_file = open(metadata_file, "r")
        metadata_content = open_metadata_file.read()
        open_metadata_file.close()

        tree = ET.fromstring(metadata_content)
        for c in tree:
            metadata_dict[c.tag] = c.text.strip()
    else:
        print("Metadata not found")
    return metadata_dict


def get_checksum(file):
    sha256_hash = hashlib.sha256()
    with open(file, "rb") as f:
        for byte_block in iter(lambda: f.read(4096), b""):
            sha256_hash.update(byte_block)
        return sha256_hash.hexdigest()


def create_fse(_directory):
    """
        Recursive call to create File System Entry objects for directories
        and files/items and return the resulting final FSE object.
        Also adds dmdsec to the file when metadata file found
    """

    non_root_directory = _directory[len(sip_directory + "/"):]
    _fse = metsrw.FSEntry(label=_directory, path=non_root_directory, type="Directory", file_uuid=str(uuid.uuid4()))
    for file in os.listdir(_directory):
        file_path = f"{_directory}/{file}"
        non_root_file_path = f"{non_root_directory}/{file}"
        if os.path.isdir(file_path):
            _fse.children.append(create_fse(file_path))
        elif os.path.isfile(file_path):
            file_use = non_root_file_path.split("/")[0].capitalize()
            this_file_data = file_data[file_path]
            if file_use in ["Documentation", "Schemas", "Representations", "Data"]:
                if not (file_use == "Representations" and file != "METS.xml"):
                    if file_use == "Data":
                        file_use = non_root_directory
                    _fse.children.append(metsrw.FSEntry(label=_directory,
                                                        path=non_root_file_path,
                                                        type="Item",
                                                        file_uuid=str(uuid.uuid4()),
                                                        # file_uuid=str(metsrw.IdGenerator("file")),
                                                        use=file_use,
                                                        checksum=get_checksum(file_path),
                                                        checksumtype="SHA-256",
                                                        derived_from=_fse,
                                                        mimetype=this_file_data['mimetype'],
                                                        filesize=str(this_file_data['filesize']),
                                                        created=this_file_data['modified'],  # use last_modified
                                                        # lastmodified=file_dict['lastmodified']
                                                        ))
    return _fse


def add_metadata():
    """
        Adds altIDs and agent infomation to the mets header
        if present the systemconfig file
    """
    if metadata.get('softwareVersion', "") != "":
        mets.agents.append(
            metsrw.Agent("CREATOR", type="SOFTWARE", name="piql eHealth SIP Creator",
                         notes=[metadata['softwareVersion']],
                         csip_notetype="SOFTWARE VERSION"))

    if "/representations/" not in sip_directory:
        # Header
        alt_ids = ['submissionagreement', 'referencecode', 'previoussubmissionagreement', 'previousreferencecode']

        for i in alt_ids:
            if metadata.get(i, "") != "":
                mets.alternate_ids.append(
                    metsrw.AltRecordID(metadata[i], type=i.upper()))

        if metadata.get("creatorName", "") != "" and metadata.get("creatorID", "") != "":
            mets.agents.append(
                metsrw.Agent("CREATOR", type="ORGANIZATION", name=metadata['creatorName'],
                             notes=[metadata['creatorID']],
                             csip_notetype="IDENTIFICATIONCODE"))
        if metadata.get("submitterName", "") != "" and metadata.get("submitterdetails", "") != "":
            mets.agents.append(
                metsrw.Agent("SUBMITTER", type="INDIVIDUAL", name=metadata['submitterName'],
                             notes=[metadata['submitterdetails']]))
        if metadata.get("archiveName", "") != "" and metadata.get("archiveID", "") != "":
            mets.agents.append(
                metsrw.Agent("ARCHIVIST", type="ORGANIZATION", name=metadata['archiveName'],
                             notes=[metadata['archiveID']],
                             csip_notetype="IDENTIFICATIONCODE"))
        if metadata.get("preservation", "") != "" and metadata.get("preservationID", "") != "":
            mets.agents.append(
                metsrw.Agent("PRESERVATION", type="ORGANIZATION", name=metadata['preservation'],
                             notes=[metadata['preservationID']],
                             csip_notetype="IDENTIFICATIONCODE"))


def add_dmdsec():
    metadata_directory = f"{sip_directory}/metadata/descriptive"
    if os.path.isdir(metadata_directory):
        for file in os.listdir(metadata_directory):
            othermdtype = "FHIR.Patient"
            if "/representations/" not in metadata_directory:
                if metadata.get('patientschemaName', "") != "":
                    othermdtype = metadata['patientschemaName']
            else:
                if metadata.get('patientclinicalschemaName', "") != "":
                    othermdtype = metadata['patientclinicalschemaName']
            metadata_file_data = file_data[f"{metadata_directory}/{file}"]
            fse.add_dmdsec(f"metadata/descriptive/{file}", "OTHER", mode='mdref', loctype="URL",
                           othermdtype=othermdtype,
                           mimetype=metadata_file_data['mimetype'],
                           size=str(metadata_file_data['filesize']),
                           created=metadata_file_data['modified'],
                           checksum=get_checksum(f"{metadata_directory}/{file}"),
                           checksumtype="SHA-256"
                           )


def delete_unnecessary_files():
    if os.path.isfile(f"{sip_directory}/metadata.xml"):
        os.remove(f"{sip_directory}/metadata.xml")
        print("Metadata removed")


if __name__ == '__main__':
    if len(sys.argv[1:]) >= 1:
        sip_directory = sys.argv[1].replace("\\", "/")
        if os.path.isdir(sip_directory):
            if os.path.isdir(f"{sip_directory}/representations"):
                for rep_file in os.listdir(f"{sip_directory}/representations"):
                    subprocess.call(
                        ["python", sys.argv[0], os.path.normpath(f"{sip_directory}/representations/{rep_file}")])
                    print()
            elif "representations" in sip_directory:
                record = sip_directory.split("\\").pop()

            # Override necessary metsrw classes
            metsrw.Agent = metsrw_override.Agent
            metsrw.SubSection = metsrw_override.SubSection
            metsrw.MDRef = metsrw_override.MDRef
            metsrw.METSDocument = metsrw_override.METSDocument
            metsrw.FSEntry = metsrw_override.FSEntry

            current_directory = os.path.dirname(sys.argv[0].replace("\\", "/"))

            file_data = gen_sf_file_data()
            metadata = get_metadata()

            # Add schema link to xsi:schemaLocation
            if "/representations/" not in sip_directory:
                if metadata.get("patientschemaDataLink", "") != "":
                    metsrw.utils.SCHEMA_LOCATIONS = (metsrw.utils.SCHEMA_LOCATIONS
                                                     + metadata["patientschemaDataLink"])
            else:
                if metadata.get("patientclinicalschemaDataLink", "") != "":
                    metsrw.utils.SCHEMA_LOCATIONS = (metsrw.utils.SCHEMA_LOCATIONS
                                                     + metadata["patientclinicalschemaDataLink"])

            mets = metsrw.METSDocument()
            fse = create_fse(sip_directory)

            add_dmdsec()
            mets.append(fse)
            add_metadata()

            mets.objid = os.path.basename(os.path.normpath(sip_directory))

            mets.write(f"{sip_directory}/METS.xml", pretty_print=True)
            print(f"Written {sip_directory}/METS.xml")

            if DELETE_METADATA_FILE:
                delete_unnecessary_files()
            
        else:
            print("Error: directory not found")
    else:
        print("Error: no directory given")
