import json
import os
import platform
import shutil
from pathlib import Path

from setuptools import find_packages, setup
from setuptools.command.build_py import build_py

MODEL_SOURCE_DIR = "orbit/stan"
MODELS = ["dlt", "ets", "lgt", "ktrlite"]

DESCRIPTION = "Orbit is a package for Bayesian time series modeling and inference."
BINARIES = ["diagnose", "print", "stanc", "stansummary"]

with open("orbit/config.json") as f:
    config = json.load(f)
CMDSTAN_VERSION = config["CMDSTAN_VERSION"]
IS_WINDOWS = platform.platform().startswith("Win")


def read_long_description(filename="README.md"):
    # with open(filename) as f:
    with open("README.md", "r", encoding="utf-8") as f:
        return f.read().strip()


def requirements(filename="requirements.txt"):
    with open(filename) as f:
        return f.readlines()


def remove_older_cmdstan(cmdstan_version):
    """
    Checks the default CmdStan path (~/.cmdstan) for folders with older versions
    and removes them.

    Args:
        cmdstan_version (str): The current CmdStan version.
    """
    default_path = os.path.expanduser("~/.cmdstan")

    if not os.path.exists(default_path):
        # Path doesn't exist, nothing to remove
        return

    for folder in os.listdir(default_path):
        if folder.startswith("cmdstan-"):
            # Extract version number from folder name
            folder_version = folder.split("-")[1]
            if folder_version < cmdstan_version:
                # Remove folder if version is older
                full_path = os.path.join(default_path, folder)
                try:
                    print(f"Removing older CmdStan version: {folder} : {full_path}")
                    shutil.rmtree(full_path)
                    print(f"Done.")
                except OSError as e:
                    print(f"Error removing {folder}: {e}")


def install_stan():
    """
    Compile and install stan backend
    Reference from prophet
    """
    from multiprocessing import cpu_count
    import cmdstanpy

    remove_older_cmdstan(CMDSTAN_VERSION)

    if not cmdstanpy.install_cmdstan(
        version=CMDSTAN_VERSION,
        overwrite=True,
        verbose=True,
        cores=cpu_count(),
        progress=True,
        compiler=IS_WINDOWS,
    ):
        raise RuntimeError("CmdStan failed to install in repackaged directory")

    print("Installed cmdstanpy package.")


def build_model(model: str, model_dir: str):
    import cmdstanpy

    # compile stan file in place
    model_name = f"{model}.stan"
    model_path = os.path.join(model_dir, model_name)
    print(f"Compiling stan file in place: {model_path}")
    _ = cmdstanpy.CmdStanModel(stan_file=model_path, force_compile=True)


def build_stan_models():
    for model in MODELS:
        # note: ensure copy target is a directory not a file.
        build_model(
            model=model,
            model_dir=MODEL_SOURCE_DIR,
        )


class BuildPyCommand(build_py):
    """Custom build command to make sure install cmdstanpy properly."""

    def run(self):
        print("Running build py command.")
        if not self.dry_run:
            print("Not a dry run, run with build")
            # build_stan_model(target_dir)

            # install cmdstan and compilers, in the default directory for simplicity
            install_stan()

            # build all stan models
            build_stan_models()

        print("Dry run.")
        build_py.run(self)


about = {}
here = Path(__file__).parent.resolve()
with open(here / "orbit" / "__version__.py", "r") as f:
    exec(f.read(), about)


setup(
    version=about["__version__"],
    packages=find_packages(),
    name="orbit-ml",
    description=DESCRIPTION,
    include_package_data=True,
    install_requires=requirements("requirements.txt"),
    tests_require=requirements("requirements-test.txt"),
    cmdclass={
        "build_py": BuildPyCommand,
    },
    test_suite="orbit.tests",
    long_description=read_long_description(),
    long_description_content_type="text/markdown",
    url="https://orbit-ml.readthedocs.io/en/stable/",
    zip_safe=False,
)
