import codecs
import json
import locale
import os
import platform
import struct
import sys
from typing import Dict, Optional, Union

from pandas._typing import JSONSerializable
from pandas.compat._optional import VERSIONS, _get_version, import_optional_dependency


def _get_commit_hash() -> Optional[str]:
    """
    Use vendored versioneer code to get git hash, which handles
    git worktree correctly.
    """
    from pandas._version import get_versions

    versions = get_versions()
    return versions["full-revisionid"]


def _get_sys_info() -> Dict[str, JSONSerializable]:
    """
    Returns system information as a JSON serializable dictionary.
    """
    uname_result = platform.uname()
    language_code, encoding = locale.getlocale()
    return {
        "commit": _get_commit_hash(),
        "python": ".".join(str(i) for i in sys.version_info),
        "python-bits": struct.calcsize("P") * 8,
        "OS": uname_result.system,
        "OS-release": uname_result.release,
        "Version": uname_result.version,
        "machine": uname_result.machine,
        "processor": uname_result.processor,
        "byteorder": sys.byteorder,
        "LC_ALL": os.environ.get("LC_ALL"),
        "LANG": os.environ.get("LANG"),
        "LOCALE": {"language-code": language_code, "encoding": encoding},
    }


def _get_dependency_info() -> Dict[str, JSONSerializable]:
    """
    Returns dependency information as a JSON serializable dictionary.
    """
    deps = [
        "pandas",
        # required
        "numpy",
        "pytz",
        "dateutil",
        # install / build,
        "pip",
        "setuptools",
        "Cython",
        # test
        "pytest",
        "hypothesis",
        # docs
        "sphinx",
        # Other, need a min version
        "blosc",
        "feather",
        "xlsxwriter",
        "lxml.etree",
        "html5lib",
        "pymysql",
        "psycopg2",
        "jinja2",
        # Other, not imported.
        "IPython",
        "pandas_datareader",
    ]
    deps.extend(list(VERSIONS))

    result: Dict[str, JSONSerializable] = {}
    for modname in deps:
        mod = import_optional_dependency(
            modname, raise_on_missing=False, on_version="ignore"
        )
        result[modname] = _get_version(mod) if mod else None
    return result


def show_versions(as_json: Union[str, bool] = False) -> None:
    """
    Provide useful information, important for bug reports.

    It comprises info about hosting operation system, pandas version,
    and versions of other installed relative packages.

    Parameters
    ----------
    as_json : str or bool, default False
        * If False, outputs info in a human readable form to the console.
        * If str, it will be considered as a path to a file.
          Info will be written to that file in JSON format.
        * If True, outputs info in JSON format to the console.
    """
    sys_info = _get_sys_info()
    deps = _get_dependency_info()

    if as_json:
        j = {"system": sys_info, "dependencies": deps}

        if as_json is True:
            print(j)
        else:
            assert isinstance(as_json, str)  # needed for mypy
            with codecs.open(as_json, "wb", encoding="utf8") as f:
                json.dump(j, f, indent=2)

    else:
        assert isinstance(sys_info["LOCALE"], dict)  # needed for mypy
        language_code = sys_info["LOCALE"]["language-code"]
        encoding = sys_info["LOCALE"]["encoding"]
        sys_info["LOCALE"] = f"{language_code}.{encoding}"

        maxlen = max(len(x) for x in deps)
        print("\nINSTALLED VERSIONS")
        print("------------------")
        for k, v in sys_info.items():
            print(f"{k:<{maxlen}}: {v}")
        print("")
        for k, v in deps.items():
            print(f"{k:<{maxlen}}: {v}")


def main() -> int:
    from optparse import OptionParser

    parser = OptionParser()
    parser.add_option(
        "-j",
        "--json",
        metavar="FILE",
        nargs=1,
        help="Save output as JSON into file, pass in '-' to output to stdout",
    )

    (options, args) = parser.parse_args()

    if options.json == "-":
        options.json = True

    show_versions(as_json=options.json)

    return 0


if __name__ == "__main__":
    sys.exit(main())
