from rich import print as rprint
import git
from util import *
from jinja2 import Environment, FileSystemLoader, select_autoescape
import fire
from collections.abc import Callable
import process
import functools
import shutil
from pathlib import Path


def make_absurl_filter(site_conf: any) -> Callable[[str], str]:
    return functools.partial(get_absolute_url, site_conf)


class CLI:
    @staticmethod
    def g(*args, **kwargs):
        return CLI.generate(*args, **kwargs)

    @staticmethod
    def w(*args, **kwargs):
        return CLI.watch(*args, **kwargs)

    @staticmethod
    def generate(
        base_dir: str = "site", output_dir: str = "_dist", production: bool = False
    ):
        base_dir = Path(base_dir)
        output_dir = Path(output_dir)
        html_dir = output_dir / "html"

        if output_dir.exists() and not is_directory_empty(output_dir):
            rprint(
                WARN_LEADER
                + f"A directory called [bold]{output_dir}[/bold] already exists and is not "
                f"empty"
            )

        with open(base_dir / "config.yml") as f:
            site_config = load_yaml(f.read())
            assert (
                "build" not in site_config
            ), "build section of site config must not exist so it can be written at runtime"
            site_config["build"] = {
                "hash": git.get_latest_commit_hash(base_dir)[:7],
                "date": git.get_latest_commit_datetime(base_dir),
            }

        jinja_env = Environment(
            loader=FileSystemLoader("site/content"), autoescape=select_autoescape()
        )

        jinja_env.filters["absurl"] = make_absurl_filter(site_config)
        jinja_env.filters["fmtdate"] = lambda x: x.strftime("%Y-%m-%d")

        rprint(INFO_LEADER + "Processing content")
        process.content(base_dir, html_dir, jinja_env, site_config)
        rprint(INFO_LEADER + "Processing blog")
        process.blog(base_dir, html_dir, jinja_env, site_config)
        rprint(INFO_LEADER + "Generating Caddy config")
        process.caddy_config(base_dir, output_dir, jinja_env, site_config)
        rprint(INFO_LEADER + "Processing Sass")
        process.sass(base_dir, output_dir)

        if production:
            rprint(INFO_LEADER + "Compressing PNG")
            process.compress_png(output_dir)

        thing_overrides = {"rendered": "page", "ran": "command"}
        res_parts = []
        for (key, count) in get_counts().items():
            s = "" if count == 1 else "s"
            res_parts.append(f"{key} {count} {thing_overrides.get(key, 'file')}{s}")

        rprint(INFO_LEADER + "Finished working, " + ", ".join(res_parts))

    @staticmethod
    def watch(base_dir: str = "site", output_dir: str = "_dist"):
        import pyinotify
        import threading
        import http.server

        if os.path.exists(output_dir):
            rprint(WARN_LEADER + f"clobbering {output_dir}")

        def run():
            if os.path.exists(output_dir):
                shutil.rmtree(output_dir)
            reset_counts()  # boys and girls, this is why global state is bad
            CLI.generate(base_dir, output_dir=output_dir)

        debounced_run = debounce(1)(run)

        class OnWriteHandler(pyinotify.ProcessEvent):
            def process_IN_MODIFY(self, event):
                rprint(INFO_LEADER + f"Change detected in {event.pathname}")
                debounced_run()

        wm = pyinotify.WatchManager()
        notifier = pyinotify.Notifier(wm, default_proc_fun=OnWriteHandler())
        wm.add_watch(base_dir, pyinotify.ALL_EVENTS, rec=True, auto_add=True)

        run()

        server = http.server.HTTPServer(
            ("127.0.0.1", 8000),
            lambda a, b, c: http.server.SimpleHTTPRequestHandler(
                a, b, c, directory=Path(output_dir) / "html"
            ),
        )
        threading.Thread(target=server.serve_forever, daemon=True).start()

        rprint(INFO_LEADER + f"Listening on http://127.0.0.1:8000")
        rprint(INFO_LEADER + f"Watching {base_dir} directory for updates")
        notifier.loop()


if __name__ == "__main__":
    fire.Fire(CLI)
