import inspect
import posixpath
import subprocess
import sys
import typing
import types
import pathlib

from ..pyproject import PyProject


class ImportCreator:
    @classmethod
    def add_subparser(cls, parent_parser, subparsers):
        parser = subparsers.add_parser(
            "create-imports",
            help="Generate suitable imports for a module. Prefer using update-init",
            parents=[parent_parser],
        )
        parser.add_argument("base", help="Ex: wpiutil")
        parser.add_argument("compiled", nargs="?", help="Ex: wpiutil._impl.wpiutil")
        parser.add_argument(
            "--write", "-w", action="store_true", help="Modify existing __init__.py"
        )
        parser.add_argument(
            "--override_output_file",
            type=pathlib.Path,
            help="If specified, will write the updated file to this location instead of making the change in place. Only used if --write is set.",
        )
        return parser

    def _rel(self, base: str, compiled: str) -> str:
        base = posixpath.join(*base.split("."))
        compiled = posixpath.join(*compiled.split("."))
        elems = posixpath.relpath(compiled, base).split("/")
        elems = ["" if e == ".." else e for e in elems]
        return f".{'.'.join(elems)}"

    def run(self, args):
        self.create(args.base, args.compiled, args.write, args.override_output_file)

    def create(
        self,
        base: str,
        compiled: typing.Optional[str],
        write: bool,
        override_output_file: typing.Optional[pathlib.Path],
    ):
        # Runtime Dependency Check
        try:
            import black
        except:
            print(
                "Error, The following module is required to run this tool: black",
                file=sys.stderr,
            )
            return 1

        if not compiled:
            compiled = f"{base}._{base.split('.')[-1]}"
            print(f"CHK base={base} compiled={compiled} (auto)")
        else:
            print(f"CHK base={base} compiled={compiled}")

        # TODO: could probably generate this from parsed code, but seems hard
        ctx = {}
        exec(f"from {compiled} import *", {}, ctx)
        for k in list(ctx.keys()):
            if isinstance(ctx[k], types.ModuleType):
                del ctx[k]

        relimport = self._rel(base, compiled)

        stmt_compiled = "" if not compiled else f" {compiled}"
        begin_stmt = f"# autogenerated by 'semiwrap create-imports {base}"
        old_begin_stmt = f"# autogenerated by 'robotpy-build create-imports {base}"

        stmt = inspect.cleandoc(
            f"""

            {begin_stmt}{stmt_compiled}'
            from {relimport} import {','.join(sorted(ctx.keys()))}
            __all__ = ["{'", "'.join(sorted(ctx.keys()))}"]
        
        """
        )

        content = subprocess.check_output(
            ["black", "-", "-q"], input=stmt.encode("utf-8")
        ).decode("utf-8")

        if write:
            fctx = {}
            exec(f"from {base} import __file__", {}, fctx)
            fname = fctx["__file__"]

            with open(fname) as fp:
                fcontent = orig_content = fp.read()

            # Find the beginning statement
            idx = startidx = fcontent.find(begin_stmt)
            if startidx == -1:
                idx = startidx = fcontent.find(old_begin_stmt)

            if startidx != -1:
                for to_find in ("from", "__all__", "[", "]", "\n"):
                    idx = fcontent.find(to_find, idx)
                    if idx == -1:
                        startidx = -1
                        break

            if startidx == -1:
                # If not present, just append and let the user figure it out
                fcontent = fcontent + "\n" + content
            else:
                fcontent = fcontent[:startidx] + content + fcontent[idx + 1 :]

            # Always write the file if override_output_file is specified.
            # Othewise only write if the contents have changed.
            if override_output_file or fcontent != orig_content:
                fname = override_output_file or fname
                with open(fname, "w") as fp:
                    fp.write(fcontent)
                print("MOD", base)
            else:
                print("OK", base)

        else:
            print(content)


class UpdateInit:
    @classmethod
    def add_subparser(cls, parent_parser, subparsers):
        parser = subparsers.add_parser(
            "update-init",
            help="Updates __init__.py files using settings from tool.semiwrap.update_init",
            parents=[parent_parser],
        )
        return parser

    def run(self, args):
        project = PyProject().project

        if project.update_init is None:
            print("[tool.semiwrap].update_init not set", file=sys.stderr)
            return 1

        ic = ImportCreator()

        for to_update in project.update_init:
            if " " in to_update:
                base, compiled = to_update.split(" ", 1)
            else:
                base = to_update
                compiled = None

            ic.create(base, compiled, True)
