#!/usr/bin/python3


import logging
from pathlib import Path
from typing import Any

from slpkg.config import config_load
from slpkg.load_data import LoadData
from slpkg.mark import Mark
from slpkg.repositories import Repositories
from slpkg.utilities import Utilities

logger = logging.getLogger(__name__)


class Export:  # pylint: disable=[R0902,R0903]
    """Exports manually installed packages to a .pkgs file."""

    def __init__(self, options: dict[str, Any]) -> None:
        self.options = options
        self.utils = Utilities()
        self.repos = Repositories()
        # Mark expects (packages, options). We pass empty list for reading only.
        self.mark = Mark([], options)

        self.cyan = config_load.cyan
        self.endc = config_load.endc
        self.red = config_load.red
        self.deps_log_file = config_load.deps_log_file

    def _get_manually_installed(self) -> list[str]:
        """Determine which packages were manually installed."""
        # 1. Get packages from marks.log (Explicitly marked by user)
        marked_packages: set[str] = self.mark._read_marks_log()  # pylint: disable=[W0212]
        logger.debug("Found %d marked packages.", len(marked_packages))

        # 2. Get all installed packages
        installed_packages = self.utils.all_installed()

        # 3. Analyze dependencies to find top-level packages
        deps_data: dict[str, list[str]] = self.utils.read_json_file(self.deps_log_file)  # type: ignore

        all_dependencies: set[str] = set()
        for deps in deps_data.values():
            all_dependencies.update(deps)

        # Top-level packages from deps.log
        top_level = {pkg for pkg in deps_data if pkg not in all_dependencies}

        # 4. Filter: Only keep top-level if they are NOT from official repo
        # OR if they are explicitly marked.
        export_list: set[str] = set(marked_packages)

        for pkg_name in top_level:
            if pkg_name in installed_packages:
                full_name = installed_packages[pkg_name]
                parsed = self.utils.split_package(full_name)
                tag = parsed.get("tag", "")

                # Heuristic: packages with tags are usually the ones to replicate.
                if tag or pkg_name in marked_packages:
                    export_list.add(pkg_name)

        # Final check against currently installed
        installed_names = set(installed_packages.keys())
        result = sorted([pkg for pkg in export_list if pkg in installed_names])

        logger.info("Total packages for export: %d", len(result))
        return result

    def _format_export_lines(self, export_list: list[str]) -> list[str]:  # pylint: disable=[R0914]
        """Format the grouped package list with comments."""
        deps_data: dict[str, list[str]] = self.utils.read_json_file(self.deps_log_file)  # type: ignore
        installed = self.utils.all_installed()

        # Build pkg → repo mapping from each repo's data.json.
        # Repos appear in dict order (sbo, ponce, slack, slack_extra, slack_patches, …)
        # so later entries override earlier ones — slack_patches correctly wins over slack.
        pkg_to_repo: dict[str, str] = {}
        for repo_name, cfg in self.repos.repositories.items():
            if not cfg.get("enable"):
                continue
            data_file: Path = cfg["path"] / self.repos.data_json
            if data_file.is_file():
                for pkg_name in LoadData.read_data_file(data_file):
                    pkg_to_repo[pkg_name] = repo_name

        # Group packages by repository name
        repos_group: dict[str, list[str]] = {}
        for pkg in export_list:
            if pkg in pkg_to_repo:
                repo_name = pkg_to_repo[pkg]
            else:
                # Fallback for packages not found in any repo data (e.g. manually installed)
                tag = self.utils.split_package(installed.get(pkg, "")).get("tag", "slack").lower()
                clean_tag = "".join(filter(str.isalpha, tag))
                repo_name = clean_tag if clean_tag else "slack"

            if repo_name not in repos_group:
                repos_group[repo_name] = []
            repos_group[repo_name].append(pkg)

        ver_path = Path("/etc/slackware-version")
        slk_ver = ver_path.read_text(encoding="utf-8").strip() if ver_path.exists() else "Slackware"
        lines = ["# slpkg export list", f"# Generated on {slk_ver}",
                 "# Format: packages grouped by repository, dependencies as comments", ""]

        for tag in sorted(repos_group.keys()):
            lines.append(f"# [{tag}]")
            for pkg in repos_group[tag]:
                lines.append(pkg)
                if not self.options.get("option_no_deps"):
                    for dep in deps_data.get(pkg, []):
                        lines.append(f"# {dep}")
            lines.append("")
        return lines

    def run(self, output_file: str) -> None:
        """Main execution logic for export."""
        export_list = self._get_manually_installed()

        if not export_list:
            print(f"\n{self.red}Error{self.endc}: No manually installed packages found to export.")
            return

        out_path = Path(output_file)
        if out_path.suffix != ".pkgs":
            out_path = out_path.with_suffix(".pkgs")

        try:
            lines = self._format_export_lines(export_list)
            out_path.write_text("\n".join(lines).strip() + "\n", encoding="utf-8")
            print(f"Successfully exported {self.cyan}{len(export_list)}{self.endc} packages to: "
                  f"{self.cyan}{out_path}{self.endc}")
            logger.info("Exported %d packages to %s", len(export_list), out_path)
        except OSError as e:
            print(f"{self.red}Error{self.endc}: Failed to write to file '{out_path}': {e}")
            logger.error("Failed to write export file %s: %s", out_path, e)
