import argparse
from typing import Dict, Tuple

from pyDataverse.api import DataAccessApi, NativeApi


def prepare_apis(dataverse_url: str, api_token: str) -> Tuple[DataAccessApi, NativeApi]:
    """Prepares the API objects for the requests.

    Args:
        dataverse_url (str): URL of the dataverse instance.
        api_token (str): API token to use for the requests.

    Returns:
        (DataAccessApi, NativeApi): API objects to use for the requests.
    """

    data_api = DataAccessApi(dataverse_url, api_token)
    native_api = NativeApi(dataverse_url, api_token)

    return data_api, native_api


def available_versions(
    dataset_pid: str,
    api: NativeApi,
) -> Dict[str, Dict]:
    """Fetches all available versions of a dataset.

    Args:
        dataset_pid (str): Persistent identifier of the dataset.
        api (NativeApi): API object to use for the request.

    Returns:
        Dict[str, Dict]: Mapping of version number to dataset metadata.
    """

    response = api.get_dataset_versions(dataset_pid)

    if response.status_code != 200:
        raise Exception(f"Error getting dataset versions: {response.text}")

    return {
        str(dataset["versionNumber"]): dataset for dataset in response.json()["data"]
    }


def get_file_by_dataset_version(
    datasets: Dict[str, Dict],
    fname: str,
    version: str,
    data_api: DataAccessApi,
) -> str:
    """Fetches a file from a dataset version, given it exists.

    Args:
        datasets (Dict[str, Dict]): Fetched dataset metadata for each version.
        fname (str): Name of the file to fetch.
        version (str): Version of the dataset to fetch the file from.
        data_api (DataAccessApi): API object to use for the request.

    Returns:
        str: Raw content of the file
    """

    if isinstance(version, (int, float)):
        version = str(version)

    if version not in datasets:
        raise ValueError(f"Version {version} not found")

    # Get the version of desire
    dataset = datasets[version]

    # Retrieve the file metadata
    file_meta = list(filter(lambda fmeta: fmeta["label"] == fname, dataset["files"]))

    if not file_meta:
        raise ValueError(f"File {fname} not found")
    elif len(file_meta) > 1:
        # Should not occur, but you never know
        raise ValueError(f"Multiple files with name {fname} found")

    # Fetch the file and return the raw data
    response = data_api.get_datafile(file_meta[0]["dataFile"]["id"])

    return response.text


def main(
    dataset_pid: str,
    path: str,
    fname: str,
    version: str,
    api_token: str,
    server_url: str,
    show_versions: bool = False,
):
    # Prepare the APIs
    data_api, native_api = prepare_apis(server_url, api_token)

    # Get all the available versions
    versions = available_versions(dataset_pid, native_api)

    if show_versions:
        print("Available versions:")
        print(", ".join(versions.keys()))
        return

    # Get the file from the desired version
    file_content = get_file_by_dataset_version(versions, fname, version, data_api)

    if path is None:
        path = "./" + fname

    with open(path, "w") as f:
        f.write(file_content)


if __name__ == "__main__":
    USAGE = """
    1) Display available versions of a dataset:
    
        python fetch_file_by_version.py --server https://demo.dataverse.org --pid doi:10.70122/FK2/W5AGKD --show-versions
    
    2) Fetch a file from a dataset version:
    
        python fetch_file_by_version.py --server https://demo.dataverse.org --pid doi:10.70122/FK2/W5AGKD --fname test.txt --version 2
    
    """

    argparser = argparse.ArgumentParser(
        prog="Fetch file by version",
        description="Fetches a file from a dataset version, given it exists.",
        epilog=USAGE,
    )

    argparser.add_argument("--pid", help="Persistent identifier of the dataset")
    argparser.add_argument("--path", help="Path to save the file to")
    argparser.add_argument("--fname", help="Name of the file to fetch")
    argparser.add_argument(
        "--version", help="Version of the dataset to fetch the file from"
    )
    argparser.add_argument("--token", help="API token to use for the requests")
    argparser.add_argument("--server", help="URL of the dataverse instance")
    argparser.add_argument(
        "--show-versions",
        action="store_true",
        help="Show available versions of the dataset",
    )

    # Get all the arguments
    args = argparser.parse_args()
    pid = args.pid
    path = args.path
    fname = args.fname
    version = args.version
    token = args.token
    server = args.server
    show_versions = args.show_versions

    main(
        dataset_pid=pid,
        path=path,
        fname=fname,
        version=version,
        api_token=token,
        server_url=server,
        show_versions=show_versions,
    )
