#!/usr/bin/python3


from __future__ import annotations

import logging
import re
import shutil
import sys
from pathlib import Path
from typing import Any

from slpkg.config import config_load
from slpkg.history import History
from slpkg.load_data import LoadData
from slpkg.local_install import LocalInstall
from slpkg.remove_packages import RemovePackages
from slpkg.utilities import Utilities
from slpkg.views.display import View

logger = logging.getLogger(__name__)


class Rollback:  # pylint: disable=[R0902,R0903]
    """Provides functionality to undo the last package operation."""

    def __init__(self, options: dict[str, Any]) -> None:
        self.options = options
        # History expects limit: int
        self.history = History(limit=0)
        self.utils = Utilities()
        self.view = View(options)
        self.history_file = config_load.history_log_file

        self.cyan = config_load.cyan
        self.green = config_load.green
        self.yellow = config_load.yellow
        self.red = config_load.red
        self.endc = config_load.endc
        self.bold = config_load.bold
        self.package_types = config_load.package_type

    def _parse_last_transaction(self) -> list[dict[str, str]]:
        """Identify the group of actions from the last execution."""
        if not self.history_file.is_file():
            return []

        try:
            lines = self.history_file.read_text(encoding="utf-8").splitlines()
            if not lines:
                return []

            last_line = lines[-1]
            ts_match = re.match(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2})", last_line)
            if not ts_match:
                return []

            last_ts = ts_match.group(1)
            transaction_reversed = []
            first_action = ""

            for line in reversed(lines):
                if line.startswith(last_ts):
                    parts = [p.strip() for p in line.split("|")]
                    if len(parts) >= 5:
                        current_action = parts[1].upper()
                        if not first_action:
                            first_action = current_action

                        if current_action != first_action:
                            break

                        pkg_entry = {
                            "action": current_action,
                            "package": parts[2],
                            "version": parts[3],
                            "repo": parts[4]
                        }
                        if pkg_entry not in transaction_reversed:
                            transaction_reversed.append(pkg_entry)
                else:
                    break

            # transaction_reversed is already in the correct order for UNDO
            return transaction_reversed

        except (OSError, AttributeError, IndexError) as e:
            logger.error("Error parsing history for rollback: %s", e)
            return []

    def _find_local_package_file(self, pkg_name: str, version: str) -> Path | None:
        """Search for a built package file in common temporary locations."""
        search_paths = [Path("/tmp"), config_load.tmp_slpkg, config_load.build_path]

        for path in search_paths:
            if not path.is_dir():
                continue
            try:
                # Case-insensitive robust search
                for found in path.iterdir():
                    if found.is_file() and found.suffix in self.package_types:
                        if found.name.lower().startswith(f"{pkg_name.lower()}-{version.lower()}"):
                            return found
            except (OSError, PermissionError):
                continue
        return None

    def _list_transactions(self, limit: int = 10) -> None:
        """Display the most recent transactions from history."""
        if not self.history_file.is_file():
            print("No history log found.")
            return

        lines = self.history_file.read_text(encoding="utf-8").splitlines()
        if not lines:
            print("History log is empty.")
            return

        groups: dict[str, list[dict[str, str]]] = {}
        for line in reversed(lines):
            ts_match = re.match(r"^(\d{4}-\d{2}-\d{2} \d{2}:\d{2})", line)
            if ts_match:
                ts = ts_match.group(1)
                parts = [p.strip() for p in line.split("|")]
                if len(parts) >= 5:
                    if ts not in groups:
                        groups[ts] = []
                    groups[ts].append({
                        "action": parts[1].upper(),
                        "package": parts[2],
                        "version": parts[3],
                        "repo": parts[4]
                    })

        print(f"{self.bold}Recent Transactions:{self.endc}")
        columns = shutil.get_terminal_size().columns
        print("=" * (columns - 1))

        count = 0
        for ts, entries in groups.items():
            if count >= limit:
                break

            action = entries[0]["action"]
            color = self.green if action in ("INSTALL", "UPGRADE", "REINSTALL") else self.red
            pkgs = ", ".join(e["package"] for e in entries)
            max_pkg_width = columns - 35
            if len(pkgs) > max_pkg_width:
                pkgs = pkgs[:max_pkg_width - 3] + "..."

            print(f"{ts} | {color}{action:<9}{self.endc} | {pkgs}")
            count += 1

        print(f"\nUse '{self.bold}slpkg rollback{self.endc}' to undo the most recent transaction.")

    def run(self) -> None:
        """Main execution logic for rollback."""
        if self.options.get("option_list"):
            self._list_transactions()
            sys.exit(0)

        transaction = self._parse_last_transaction()
        if not transaction:
            print("No recent transactions found in history.")
            return

        print(f"{self.bold}Last Transaction identified:{self.endc}")
        action_type = transaction[0]["action"]

        for entry in transaction:
            color = self.green if entry["action"] in ("INSTALL", "UPGRADE", "REINSTALL") else self.red
            print(f"  - {color}{entry['action']}{self.endc}: {entry['package']} ({entry['repo']})")

        if action_type == "BUILD":
            print(f"\n{self.yellow}BUILD actions cannot be rolled back — no system changes were made.{self.endc}")
            return

        print(
            f"\nRollback will {self.bold}{'REMOVE' if action_type in ('INSTALL', 'UPGRADE', 'REINSTALL') else 'REINSTALL'}{self.endc} these packages.")

        self.view.question("Do you want to proceed with rollback?")

        if action_type in ("INSTALL", "UPGRADE", "REINSTALL"):
            pkgs_to_remove = [t["package"] for t in transaction]
            print(f"\n{self.red}Removing{self.endc} packages...")

            remover = RemovePackages(pkgs_to_remove, self.options)

            # Inject correct repository info from history into the view data
            for entry in transaction:
                pkg_name = self.utils.split_package(entry["package"])["name"]
                remover.view.data[pkg_name] = {"repository": entry["repo"]}

            remover.remove()
        elif action_type == "REMOVE":
            self._handle_reinstall(transaction)

    def _handle_reinstall(self, transaction: list[dict[str, str]]) -> None:  # pylint: disable=[R0914]
        """Handle reinstallation of removed packages, prioritizing local files."""
        print(f"\n{self.green}Reinstalling{self.endc} packages...")

        local_files = []
        missing_locally = []

        for entry in transaction:
            found = self._find_local_package_file(entry["package"], entry["version"])
            if found:
                local_files.append(str(found))
            else:
                missing_locally.append(entry)

        # 1. Reinstall found local files first
        if local_files:
            print(f"Found {len(local_files)} packages in temporary storage. Reinstalling...")
            LocalInstall(self.options, local_files).run()

        # 2. For those not found, provide the manual command with repo detection
        if missing_locally:
            print(f"\n{self.red}Warning{self.endc}: Could not find {len(missing_locally)} packages locally.")
            print("To reinstall them from their original repositories, run:")

            loader = LoadData()
            all_repos_data = loader.load("*", message=False)

            repo_suggestions: dict[str, list[str]] = {}

            for m in missing_locally:
                pkg = m["package"]
                repo = m["repo"]

                if repo == "local":
                    for r_name, r_data in all_repos_data.items():
                        if pkg in r_data:
                            repo = r_name
                            break

                if repo not in repo_suggestions:
                    repo_suggestions[repo] = []
                repo_suggestions[repo].append(pkg)

            for repo, pkgs in repo_suggestions.items():
                repo_flag = f" -o {repo}" if repo != "local" else ""
                pkg_list = " ".join(pkgs)
                print(f"  {self.bold}slpkg install {pkg_list}{repo_flag}{self.endc}")
