"""CLI entry point for ``python -m utils.install.lint``.

Mirrors the prior ``scripts/install/lint.sh`` interface: no-arg / ``all``
runs every group + manages the stamp file; explicit groups bypass the
stamp; ``--force`` (as first arg) drops the stamp and mmdc's browser cache
and reinstalls.
"""

from __future__ import annotations

import contextlib
import hashlib
import os
import shutil
import sys
from pathlib import Path

from utils.cache import PROJECT_ROOT
from utils.install.lint import (
    actionlint,
    eslint,
    hadolint,
    markdownlint_cli2,
    mbake,
    mermaid_cli,
    packages,
    php,
    playwright,
    ruby,
    ruff,
    shellcheck,
    shfmt,
    sqlfluff,
    stylelint,
)
from utils.install.lint.ansible import collections as ansible_collections
from utils.install.lint.ansible import commands as ansible_commands
from utils.install.lint.ansible import lint as ansible_lint
from utils.install.primitives import warn
from utils.roles.mapping import ROLE_FILE_META_SERVICES

_STAMP_KEY = hashlib.sha256(sys.executable.encode("utf-8")).hexdigest()[:8]
_STAMP = f"build/install-lint-{_STAMP_KEY}.stamp"
_PINS = f"roles/sys-lint/{ROLE_FILE_META_SERVICES}"
_STAMP_DEPS = (
    "scripts/install/lint.sh",
    "pyproject.toml",
    _PINS,
)

_STAMP_TOOLS = (
    "actionlint",
    "ansible-lint",
    "ansible-playbook",
    "eslint",
    "hadolint",
    "markdownlint-cli2",
    "mbake",
    "php",
    "ruby",
    "ruff",
    "shellcheck",
    "shfmt",
)


def _install_action_tools() -> None:
    actionlint.ensure()


def _install_ansible_tools() -> None:
    ansible_commands.ensure()
    ansible_collections.ensure()
    ansible_lint.ensure()


def _install_python_tools() -> None:
    shfmt.ensure()
    ruff.ensure()


def _install_php_tools() -> None:
    php.ensure()


def _install_ruby_tools() -> None:
    ruby.ensure()


def _install_shellcheck_tools() -> None:
    shellcheck.ensure()


def _install_dockerfile_tools() -> None:
    hadolint.ensure()


def _install_markdown_tools() -> None:
    markdownlint_cli2.ensure()


def _install_mermaid_tools() -> None:
    mermaid_cli.ensure()


def _install_makefile_tools() -> None:
    mbake.ensure()


def _install_javascript_tools() -> None:
    eslint.ensure()


def _install_playwright_tools() -> None:
    playwright.ensure()


def _install_packages_tools() -> None:
    packages.ensure()


def _install_php_tools() -> None:
    php.ensure()


def _install_ruby_tools() -> None:
    ruby.ensure()


def _install_sql_tools() -> None:
    sqlfluff.ensure()


def _install_css_tools() -> None:
    stylelint.ensure()


_GROUP_FN_NAMES = {
    "action": "_install_action_tools",
    "ansible": "_install_ansible_tools",
    "python": "_install_python_tools",
    "php": "_install_php_tools",
    "ruby": "_install_ruby_tools",
    "shellcheck": "_install_shellcheck_tools",
    "dockerfile": "_install_dockerfile_tools",
    "markdown": "_install_markdown_tools",
    "mermaid": "_install_mermaid_tools",
    "makefile": "_install_makefile_tools",
    "javascript": "_install_javascript_tools",
    "playwright": "_install_playwright_tools",
    "packages": "_install_packages_tools",
    "sql": "_install_sql_tools",
    "css": "_install_css_tools",
}


def _install_all() -> None:
    _install_action_tools()
    _install_ansible_tools()
    _install_python_tools()
    _install_php_tools()
    _install_ruby_tools()
    _install_shellcheck_tools()
    _install_dockerfile_tools()
    _install_markdown_tools()
    _install_mermaid_tools()
    _install_makefile_tools()
    _install_javascript_tools()
    _install_playwright_tools()


def _stamp_is_fresh(repo_root: Path) -> bool:
    stamp_path = repo_root / _STAMP
    if not stamp_path.is_file():
        return False
    stamp_mtime = stamp_path.stat().st_mtime
    for dep in _STAMP_DEPS:
        dep_path = repo_root / dep
        if not dep_path.is_file():
            warn(f"[install-lint] missing dependency: {dep}")
            return False
        if dep_path.stat().st_mtime > stamp_mtime:
            return False
    return (
        all(shutil.which(tool) is not None for tool in _STAMP_TOOLS)
        and mermaid_cli.browser_installed()
    )


def _touch_stamp(repo_root: Path) -> None:
    stamp_path = repo_root / _STAMP
    stamp_path.parent.mkdir(parents=True, exist_ok=True)
    with contextlib.suppress(PermissionError):
        stamp_path.parent.chmod(0o777)
    stamp_path.touch()


def _dispatch(group: str) -> None:
    if group == "all":
        _install_all()
        return
    fn_name = _GROUP_FN_NAMES.get(group)
    if fn_name is None:
        raise RuntimeError(
            "Usage: python -m utils.install.lint "
            "[all|action|ansible|python|shellcheck|dockerfile|markdown|mermaid|makefile|"
            "javascript|playwright|packages|php|ruby|sql|css]..."
        )
    globals()[fn_name]()


def main(argv: list[str] | None = None) -> int:
    args = list(sys.argv[1:] if argv is None else argv)
    repo_root = Path(PROJECT_ROOT)

    force = False
    if args and args[0] == "--force":
        force = True
        stamp_path = repo_root / _STAMP
        if stamp_path.exists():
            stamp_path.unlink()
        shutil.rmtree(mermaid_cli.browser_cache(), ignore_errors=True)
        args = args[1:]

    if not args:
        args = ["all"]

    is_all_mode = len(args) == 1 and args[0] == "all"

    if is_all_mode and not force and _stamp_is_fresh(repo_root):
        return 0

    # chdir for installers that resolve relative paths (eslint's npm ci
    previous_cwd = Path.cwd() if Path.cwd().exists() else None
    os.chdir(repo_root)
    try:
        for group in args:
            _dispatch(group)
    except RuntimeError as exc:
        warn(str(exc))
        return 1
    finally:
        if previous_cwd is not None and previous_cwd.exists():
            os.chdir(previous_cwd)

    if is_all_mode:
        _touch_stamp(repo_root)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
