# The MIT License (MIT)
#
# Copyright (c) 2018 Novo Nordisk Foundation Center for Biosustainability,
# Technical University of Denmark.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


"""Interact with the MetaNetX FTP server."""


import ftplib
import logging
from datetime import datetime
from os.path import dirname, isfile, join

import pandas as pd
from dateutil import parser


logger = logging.getLogger("populate.metanetx")


def update_file(ftp: ftplib.FTP, filename: str, last_checked: datetime) -> None:
    """
    Retrieve a file from an FTP server if it is newer than a local version.

    Parameters
    ----------
    ftp : ftplib.FTP
        An instance of the FTP server with an open connection.
    filename : str
        The file to retrieve relative to the working directory on the server.
    last_checked : datetime
        The date and time when this script was last run.

    Raises
    ------
    ftplib.all_errors
        May raise any of the errors defined in ftplib if the file doesn't
        exist or the connection fails for some other reason.

    """
    # Modified time parsing according to https://stackoverflow.com/a/29027386.
    remote_modified = parser.parse(ftp.voidcmd(f"MDTM {filename}")[4:])
    logger.info(
        f"Remote file '{filename}' last modified on "
        f"{remote_modified.isoformat()}."
    )
    local_filename = join(dirname(__file__), filename)
    if (not isfile(local_filename)) or (remote_modified > last_checked):
        logger.info("Retrieving updated file version.")
        with open(local_filename, "wb") as file_handle:
            ftp.retrbinary(f"RETR {filename}", file_handle.write)
    else:
        logger.info("Local file version is up to date.")


def update_tables(
    host: str = "ftp.vital-it.ch",
    directory: str = "databases/metanetx/MNXref/latest",
) -> None:
    """Attempt to download updated files from the MetaNetX FTP server."""
    last = join(dirname(__file__), "last.txt")
    if isfile(last):
        with open(last) as file_handle:
            last_checked = parser.parse(file_handle.read().strip())
    else:
        last_checked = datetime.fromordinal(1)
    logger.info(f"Last ran on {last_checked.isoformat()}.")
    # The files could be downloaded asynchronously, aka, premature optimization.
    files = ["README.md", "chem_xref.tsv", "chem_prop.tsv"]
    with ftplib.FTP(host) as ftp:
        # Anonymous log in is still required.
        ftp.login()
        ftp.cwd(directory)
        for filename in files:
            try:
                update_file(ftp, filename, last_checked)
            except ftplib.all_errors as error:
                logger.error(f"Failed to update '{filename}'.", exc_info=error)
    with open(last, "w") as file_handle:
        file_handle.write(datetime.utcnow().isoformat())


def load_compound_cross_references() -> pd.DataFrame:
    """Load and transform the MetaNetX chemical cross-references."""
    filename = join(dirname(__file__), "chem_xref.tsv")
    df = pd.read_table(filename, comment="#", header=None)
    df.columns = ["xref", "mnx_id", "evidence", "description"]
    # Cross references have a prefix.
    # We split the prefixes so that we know the actual data sources.
    df[["prefix", "accession"]] = df["xref"].str.split(":", n=1, expand=True)
    # MetaNetX identifiers themselves have no prefix. So we add it.
    mnx_mask = df["accession"].isnull()
    df.loc[mnx_mask, "accession"] = df.loc[mnx_mask, "prefix"]
    df.loc[mnx_mask, "prefix"] = "metanetx.chemical"
    df.loc[df["accession"] == "UNKNOWN", "accession"] = None
    logger.debug(df.head())
    return df


def load_compound_properties() -> pd.DataFrame:
    """Load and transform the MetaNetX chemical properties."""
    filename = join(dirname(__file__), "chem_prop.tsv")
    df = pd.read_table(filename, comment="#", header=None)
    df.columns = [
        "mnx_id",
        "description",
        "formula",
        "charge",
        "mass",
        "inchi",
        "smiles",
        "source",
        "inchi_key",
    ]
    logger.debug(df.head())
    return df
