"""Claude files installation step - installs sf directory files."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from installer.context import InstallContext
from installer.downloads import (
    DownloadConfig,
    FileInfo,
    download_file,
    download_files_parallel,
    get_repo_files,
)
from installer.steps.base import BaseStep
from installer.steps.claude_file_helpers import (
    _categorize_file,
    _cleanup_old_directories,
    _should_skip_file,
)

SETTINGS_FILE = "settings.json"

REPO_URL = "https://github.com/thebotclub/tribunal"


def patch_claude_paths(content: str) -> str:
    """Expand ~/.tribunal/bin/ paths to absolute paths."""
    home = Path.home()
    abs_bin_path = str(home / ".tribunal" / "bin") + "/"
    return content.replace('"~/.tribunal/bin/', '"' + abs_bin_path)


def process_settings(
    settings_content: str,
    enable_python: bool,
    enable_typescript: bool,
    enable_golang: bool,
) -> str:
    """Process settings JSON, optionally removing Python/TypeScript/Go-specific hooks."""
    config: dict[str, Any] = json.loads(settings_content)

    files_to_remove: list[str] = []
    if not enable_python:
        files_to_remove.append("file_checker_python.py")
    if not enable_typescript:
        files_to_remove.append("file_checker_ts.py")
    if not enable_golang:
        files_to_remove.append("file_checker_go.py")

    if files_to_remove:
        try:
            for hook_group in config["hooks"]["PostToolUse"]:
                hook_group["hooks"] = [
                    h for h in hook_group["hooks"] if not any(f in h.get("command", "") for f in files_to_remove)
                ]
        except (KeyError, TypeError, AttributeError):
            pass

    return json.dumps(config, indent=2) + "\n"


class ClaudeFilesStep(BaseStep):
    """Step that installs sf directory files from the repository."""

    name = "claude_files"

    def check(self, ctx: InstallContext) -> bool:
        """Check if sf files are already installed."""
        return False

    def run(self, ctx: InstallContext) -> None:
        """Install all sf files from repository."""
        ui = ctx.ui
        config = self._create_download_config(ctx)

        if ui:
            ui.status("Installing tribunal files...")

        sf_files = get_repo_files("tribunal", config)
        if not sf_files:
            self._handle_no_files(ui, config)
            return

        categories = self._categorize_files(sf_files, ctx)

        _cleanup_old_directories(ctx, config, ui)

        installed_files, file_count, failed_files = self._install_categories(categories, ctx, config, ui)

        ctx.config["installed_files"] = installed_files

        self._post_install_processing(ctx, ui)

        self._report_results(ui, file_count, failed_files)

    def _create_download_config(self, ctx: InstallContext) -> DownloadConfig:
        """Create download configuration based on context."""
        repo_branch = "main"
        if ctx.target_version:
            if ctx.target_version.startswith("dev-"):
                repo_branch = ctx.target_version
            else:
                repo_branch = f"v{ctx.target_version}"

        return DownloadConfig(
            repo_url=REPO_URL,
            repo_branch=repo_branch,
            local_mode=ctx.local_mode,
            local_repo_dir=ctx.local_repo_dir,
        )

    def _handle_no_files(self, ui: Any, config: DownloadConfig) -> None:
        """Handle case when no sf files are found."""
        if ui:
            ui.warning("No tribunal files found in repository")
            if not config.local_mode:
                ui.print("  This may be due to GitHub API rate limiting.")
                ui.print("  Try running with --local flag if you have the repo cloned.")

    def _categorize_files(self, sf_files: list[FileInfo], ctx: InstallContext) -> dict[str, list[FileInfo]]:
        """Categorize files and filter out ones to skip."""
        categories: dict[str, list[FileInfo]] = {
            "commands": [],
            "rules": [],
            "sf_plugin": [],
            "settings": [],
        }

        hooks_to_skip: list[str] = []
        if not ctx.enable_python:
            hooks_to_skip.append("file_checker_python.py")
        if not ctx.enable_typescript:
            hooks_to_skip.append("file_checker_ts.py")
        if not ctx.enable_golang:
            hooks_to_skip.append("file_checker_go.py")

        for file_info in sf_files:
            file_path = file_info.path
            if _should_skip_file(file_path, ctx, hooks_to_skip):
                continue

            category = _categorize_file(file_path)
            categories[category].append(file_info)

        return categories

    def _install_categories(
        self,
        categories: dict[str, list[FileInfo]],
        ctx: InstallContext,
        config: DownloadConfig,
        ui: Any,
    ) -> tuple[list[str], int, list[str]]:
        """Install files by category."""
        installed_files: list[str] = []
        file_count = 0
        failed_files: list[str] = []

        category_names = {
            "commands": "slash commands",
            "rules": "standard rules",
            "sf_plugin": "SF plugin files",
            "settings": "settings",
        }

        for category, file_infos in categories.items():
            if not file_infos:
                continue

            count, installed, failed = self._install_category_files(
                category, file_infos, ctx, config, ui, category_names[category]
            )
            file_count += count
            installed_files.extend(installed)
            failed_files.extend(failed)

        return installed_files, file_count, failed_files

    def _install_category_files(
        self,
        category: str,
        file_infos: list[FileInfo],
        ctx: InstallContext,
        config: DownloadConfig,
        ui: Any,
        category_display_name: str,
    ) -> tuple[int, list[str], list[str]]:
        """Install files for a single category."""
        installed: list[str] = []
        failed: list[str] = []

        def install_files() -> None:
            if category == "settings":
                for file_info in file_infos:
                    file_path = file_info.path
                    dest_file = self._get_dest_path(category, file_path, ctx)
                    success = self._install_settings(
                        file_path,
                        dest_file,
                        config,
                        ctx.enable_python,
                        ctx.enable_typescript,
                        ctx.enable_golang,
                    )
                    if success:
                        installed.append(str(dest_file))
                    else:
                        failed.append(file_path)
                return

            dest_paths = [self._get_dest_path(category, fi.path, ctx) for fi in file_infos]
            results = download_files_parallel(file_infos, dest_paths, config)

            for file_info, dest_path, success in zip(file_infos, dest_paths, results):
                if success:
                    installed.append(str(dest_path))
                else:
                    failed.append(file_info.path)

        if ui:
            with ui.spinner(f"Installing {category_display_name}..."):
                install_files()
            ui.success(f"Installed {len(file_infos)} {category_display_name}")
        else:
            install_files()

        return len(installed), installed, failed

    def _get_dest_path(self, category: str, file_path: str, ctx: InstallContext) -> Path:
        """Determine destination path based on category."""
        home_claude_dir = Path.home() / ".claude"
        home_sf_plugin_dir = home_claude_dir / "tribunal"

        if category == "commands":
            rel_path = Path(file_path).relative_to("tribunal/commands")
            return home_claude_dir / "commands" / rel_path
        elif category == "rules":
            rel_path = Path(file_path).relative_to("tribunal/rules")
            return home_claude_dir / "rules" / rel_path
        elif category == "sf_plugin":
            rel_path = Path(file_path).relative_to("tribunal")
            return home_sf_plugin_dir / rel_path
        elif category == "settings":
            return home_claude_dir / "settings.json"
        else:
            return ctx.project_dir / file_path

    def _post_install_processing(self, ctx: InstallContext, ui: Any) -> None:
        """Run post-installation processing tasks."""
        home_sf_plugin_dir = Path.home() / ".claude" / "tribunal"

        self._make_scripts_executable(home_sf_plugin_dir)

        self._update_lsp_config(home_sf_plugin_dir, ctx)

        if not ctx.local_mode:
            self._update_hooks_config(home_sf_plugin_dir, ctx)

        self._ensure_project_rules_dir(ctx)

    def _make_scripts_executable(self, plugin_dir: Path) -> None:
        """Make script files executable."""
        scripts_dir = plugin_dir / "scripts"
        if not scripts_dir.exists():
            return

        for script in scripts_dir.glob("*.cjs"):
            try:
                current_mode = script.stat().st_mode
                script.chmod(current_mode | 0o111)
            except (OSError, IOError):
                pass

    def _update_lsp_config(self, plugin_dir: Path, ctx: InstallContext) -> None:
        """Remove disabled languages from LSP config."""
        lsp_config_path = plugin_dir / ".lsp.json"
        if not lsp_config_path.exists():
            return

        try:
            lsp_config = json.loads(lsp_config_path.read_text())
            if not ctx.enable_python and "python" in lsp_config:
                del lsp_config["python"]
            if not ctx.enable_typescript and "typescript" in lsp_config:
                del lsp_config["typescript"]
            if not ctx.enable_golang and "go" in lsp_config:
                del lsp_config["go"]
            lsp_config_path.write_text(json.dumps(lsp_config, indent=2) + "\n")
        except (json.JSONDecodeError, OSError, IOError):
            pass

    def _update_hooks_config(self, plugin_dir: Path, ctx: InstallContext) -> None:
        """Remove disabled language hooks from hooks.json."""
        hooks_json_path = plugin_dir / "hooks" / "hooks.json"
        if not hooks_json_path.exists():
            return

        files_to_remove: list[str] = []
        if not ctx.enable_python:
            files_to_remove.append("file_checker_python.py")
        if not ctx.enable_typescript:
            files_to_remove.append("file_checker_ts.py")
        if not ctx.enable_golang:
            files_to_remove.append("file_checker_go.py")

        hooks_dir = plugin_dir / "hooks"
        for hook_file in files_to_remove:
            hook_path = hooks_dir / hook_file
            if hook_path.exists():
                try:
                    hook_path.unlink()
                except (OSError, IOError):
                    pass

        try:
            hooks_content = hooks_json_path.read_text()
            hooks_content = patch_claude_paths(hooks_content)
            hooks_config = json.loads(hooks_content)

            if files_to_remove:
                hooks_section = hooks_config.get("hooks", {})
                if "PostToolUse" in hooks_section:
                    for hook_group in hooks_section["PostToolUse"]:
                        if "hooks" in hook_group:
                            hook_group["hooks"] = [
                                h
                                for h in hook_group["hooks"]
                                if not any(f in h.get("command", "") for f in files_to_remove)
                            ]

            hooks_json_path.write_text(json.dumps(hooks_config, indent=2) + "\n")
        except (json.JSONDecodeError, OSError, IOError):
            pass

    def _ensure_project_rules_dir(self, ctx: InstallContext) -> None:
        """Ensure project rules directory exists."""
        project_rules_dir = ctx.project_dir / ".claude" / "rules"
        if not project_rules_dir.exists():
            project_rules_dir.mkdir(parents=True, exist_ok=True)
            (project_rules_dir / ".gitkeep").touch()

    def _report_results(self, ui: Any, file_count: int, failed_files: list[str]) -> None:
        """Report installation results."""
        if not ui:
            return

        if file_count > 0:
            ui.success(f"Installed {file_count} sf files")
        else:
            ui.warning("No tribunal files were installed")

        if failed_files:
            ui.warning(f"Failed to download {len(failed_files)} files")
            for failed in failed_files[:5]:
                ui.print(f"  - {failed}")
            if len(failed_files) > 5:
                ui.print(f"  ... and {len(failed_files) - 5} more")

    def _install_settings(
        self,
        source_path: str,
        dest_path: Path,
        config: DownloadConfig,
        install_python: bool,
        install_typescript: bool,
        install_golang: bool,
    ) -> bool:
        """Download and process settings file."""
        import tempfile

        with tempfile.TemporaryDirectory() as tmpdir:
            temp_file = Path(tmpdir) / "settings.json"
            if not download_file(source_path, temp_file, config):
                return False

            try:
                settings_content = temp_file.read_text()
                processed_content = process_settings(
                    settings_content, install_python, install_typescript, install_golang
                )
                processed_content = patch_claude_paths(processed_content)
                dest_path.parent.mkdir(parents=True, exist_ok=True)
                dest_path.write_text(processed_content)
                return True
            except (json.JSONDecodeError, OSError, IOError):
                return False
