#
# Copyright (c) 2020 Carsten Igel.
#
# This file is part of pip-licenses-reader
# (see https://github.com/carstencodes/pip-licenses-reader).
#
# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
#

"""
    Using pip-licenses, a JSON file can be created containing
    all used packages and their origins and licenses. It can be
    considered as a rough bill of materials.
    This file can be read in at run-time using this module.
"""


from typing import FrozenSet, List, Optional, NamedTuple
from pathlib import Path
from json import load as load_json
from logging import Logger
import sys
import os

from packaging.version import Version

LICENSE_FILE_JSON = "bill_of_materials.json"


class ProjectInfo(NamedTuple):
    """Information about a project. This class is immutable."""

    author: str = ""
    """The author of the package."""
    license: str = ""
    """The license which is used for publishing the package"""
    name: str = ""
    """The name of the package"""
    url: str = ""
    """The url the package can be found at."""
    version: Optional[Version] = None
    """The version of the package."""


class LicenseCollection(NamedTuple):
    """Represents a collection of projects including the licenses."""

    projects: FrozenSet[ProjectInfo] = {}
    """The list of projects used for the current application.
    """


def read_file(
    file: Optional[str] = None, log: Optional[Logger] = None
) -> LicenseCollection:
    """Reads the bill of materials JSON file generated by pip-licenses.

    Args:
        file (Optional[str], optional): The path of the file to read.
                If omitted, the file specified by 'LICENSE_FILE_JSON' is
                read next to the scripts entry-point, if the file exists.
                Defaults to None.
        log (Optional[Logger], optional): The logger to use in case of
                errors. Defaults to None.

    Returns:
        LicenseCollection: The license information for all packages.
    """
    license_file = file or __get_default_file()

    if license_file is None:
        return LicenseCollection(frozenset())

    projects: List[ProjectInfo] = []
    json_content = __read_file_content(license_file)
    if isinstance(json_content, list):
        for item in json_content:
            if isinstance(item, dict):
                try:
                    project_data = {k.lower(): v for k, v in item.items()}
                    if "version" in project_data.keys():
                        project_data["version"] = Version(project_data["version"])
                    project_info = ProjectInfo(**project_data)
                    projects.append(project_info)
                except TypeError as error:
                    if log is not None:
                        log.exception(
                            "Failed to read item from list.", exc_info=error
                        )
            else:
                log.warn(
                    "Failed to read an item, since it is not"
                    + " a string dictionary."
                )
    else:
        if log is not None:
            log.error("The parsed element is not a list")

    frozen_set = frozenset(projects)
    return LicenseCollection(frozen_set)


def __get_default_file() -> Optional[str]:
    """Gets the full path of the default JSON file, if it exists.

    Returns:
        Optional[str]: The full path of the JSON file.
    """
    script = sys.argv[0]
    script = os.path.realpath(script)
    script_dir = os.path.dirname(script)

    script_local_dir = Path(script_dir)

    path = str(script_local_dir / LICENSE_FILE_JSON)
    if os.path.isfile(path):
        return path

    return None


def __read_file_content(file: str) -> list:
    """Reads the specified json file into a dictionary.
    Args:
        file (str): The path of the file.

    Returns:
        dict: A list of string-dictionaries
    """
    with open(file, "r") as handle:
        return load_json(handle)
