# utils/fingerprint_manager.py

import os
import json
import logging
import traceback
from pathlib import Path
from typing import List, Optional

import shutil  # Ensure shutil is imported if used within the class

from utils.fingerprint import generate_fingerprint

# Instantiate the logger
logger = logging.getLogger(__name__)


class FingerprintManager:
    """
    FingerprintManager Class

    Handles operations related to fingerprints, including generation, storage,
    listing, selection, and removal. Ensures that each seed is uniquely identified
    by its fingerprint and manages the corresponding directory structure.
    """

    def __init__(self, app_dir: Path):
        """
        Initializes the FingerprintManager.

        Parameters:
            app_dir (Path): The root application directory (e.g., ~/.seedpass).
        """
        self.app_dir = app_dir
        self.fingerprints_file = self.app_dir / "fingerprints.json"
        self._ensure_app_directory()
        (
            self.fingerprints,
            self.current_fingerprint,
            self.names,
        ) = self._load_fingerprints()

    def get_current_fingerprint_dir(self) -> Optional[Path]:
        """
        Retrieves the directory path for the current fingerprint.

        Returns:
            Optional[Path]: The Path object of the current fingerprint directory or None.
        """
        if hasattr(self, "current_fingerprint") and self.current_fingerprint:
            return self.get_fingerprint_directory(self.current_fingerprint)
        else:
            logger.error("No current fingerprint is set.")
            return None

    def _ensure_app_directory(self):
        """
        Ensures that the application directory exists.
        """
        try:
            self.app_dir.mkdir(parents=True, exist_ok=True)
            logger.debug(f"Application directory ensured at {self.app_dir}")
        except Exception as e:
            logger.error(
                f"Failed to create application directory at {self.app_dir}: {e}"
            )
            raise

    def _load_fingerprints(self) -> tuple[list[str], Optional[str], dict[str, str]]:
        """Return stored fingerprints, the last used fingerprint, and name mapping."""
        try:
            if self.fingerprints_file.exists():
                with open(self.fingerprints_file, "r") as f:
                    data = json.load(f)
                fingerprints = data.get("fingerprints", [])
                current = data.get("last_used")
                names = data.get("names", {})
                logger.debug(
                    f"Loaded fingerprints: {fingerprints} (last used: {current})"
                )
                return fingerprints, current, names
            logger.debug(
                "fingerprints.json not found. Initializing empty fingerprint list."
            )
            return [], None, {}
        except Exception as e:
            logger.error(f"Failed to load fingerprints: {e}", exc_info=True)
            return [], None, {}

    def _save_fingerprints(self):
        """
        Saves the current list of fingerprints to the fingerprints.json file.
        """
        try:
            with open(self.fingerprints_file, "w") as f:
                json.dump(
                    {
                        "fingerprints": self.fingerprints,
                        "last_used": self.current_fingerprint,
                        "names": self.names,
                    },
                    f,
                    indent=4,
                )
            logger.debug(
                f"Fingerprints saved: {self.fingerprints} (last used: {self.current_fingerprint})"
            )
        except Exception as e:
            logger.error(f"Failed to save fingerprints: {e}", exc_info=True)
            raise

    def add_fingerprint(self, seed_phrase: str) -> Optional[str]:
        """
        Generates a fingerprint from the seed phrase and adds it to the list.

        Parameters:
            seed_phrase (str): The BIP-39 seed phrase.

        Returns:
            Optional[str]: The generated fingerprint or None if failed.
        """
        fingerprint = generate_fingerprint(seed_phrase)
        if fingerprint and fingerprint not in self.fingerprints:
            self.fingerprints.append(fingerprint)
            self.names.setdefault(fingerprint, "")
            self.current_fingerprint = fingerprint
            self._save_fingerprints()
            logger.info(f"Fingerprint {fingerprint} added successfully.")
            # Create fingerprint directory
            fingerprint_dir = self.app_dir / fingerprint
            fingerprint_dir.mkdir(parents=True, exist_ok=True)
            logger.debug(f"Fingerprint directory created at {fingerprint_dir}")
            return fingerprint
        elif fingerprint in self.fingerprints:
            logger.warning(f"Fingerprint {fingerprint} already exists.")
            return fingerprint
        else:
            logger.error("Fingerprint generation failed.")
            return None

    def remove_fingerprint(self, fingerprint: str) -> bool:
        """
        Removes a fingerprint and its associated directory.

        Parameters:
            fingerprint (str): The fingerprint to remove.

        Returns:
            bool: True if removed successfully, False otherwise.
        """
        if fingerprint in self.fingerprints:
            try:
                self.fingerprints.remove(fingerprint)
                self.names.pop(fingerprint, None)
                if self.current_fingerprint == fingerprint:
                    self.current_fingerprint = (
                        self.fingerprints[0] if self.fingerprints else None
                    )
                self._save_fingerprints()
                # Remove fingerprint directory
                fingerprint_dir = self.app_dir / fingerprint
                if fingerprint_dir.exists() and fingerprint_dir.is_dir():
                    for child in fingerprint_dir.glob("*"):
                        if child.is_file():
                            child.unlink()
                        elif child.is_dir():
                            shutil.rmtree(child)
                    fingerprint_dir.rmdir()
                logger.info(f"Fingerprint {fingerprint} removed successfully.")
                return True
            except Exception as e:
                logger.error(
                    f"Failed to remove fingerprint {fingerprint}: {e}", exc_info=True
                )
                return False
        else:
            logger.warning(f"Fingerprint {fingerprint} does not exist.")
            return False

    def list_fingerprints(self) -> List[str]:
        """
        Lists all available fingerprints.

        Returns:
            List[str]: A list of fingerprint strings.
        """
        logger.debug(f"Listing fingerprints: {self.fingerprints}")
        return self.fingerprints

    def select_fingerprint(self, fingerprint: str) -> bool:
        """
        Selects a fingerprint for the current session.

        Parameters:
            fingerprint (str): The fingerprint to select.

        Returns:
            bool: True if selection is successful, False otherwise.
        """
        if fingerprint in self.fingerprints:
            self.current_fingerprint = fingerprint
            self._save_fingerprints()
            logger.info(f"Fingerprint {fingerprint} selected.")
            return True
        else:
            logger.error(f"Fingerprint {fingerprint} not found.")
            return False

    def set_name(self, fingerprint: str, name: str | None) -> bool:
        """Set a custom name for a fingerprint."""
        if fingerprint not in self.fingerprints:
            return False
        if name:
            self.names[fingerprint] = name
        else:
            self.names.pop(fingerprint, None)
        self._save_fingerprints()
        return True

    def get_name(self, fingerprint: str) -> Optional[str]:
        """Return the custom name for ``fingerprint`` if set."""
        return self.names.get(fingerprint) or None

    def display_name(self, fingerprint: str) -> str:
        """Return name and fingerprint for display."""
        name = self.get_name(fingerprint)
        return f"{name} ({fingerprint})" if name else fingerprint

    def get_fingerprint_directory(self, fingerprint: str) -> Optional[Path]:
        """
        Retrieves the directory path for a given fingerprint.

        Parameters:
            fingerprint (str): The fingerprint.

        Returns:
            Optional[Path]: The Path object of the fingerprint directory or None.
        """
        fingerprint_dir = self.app_dir / fingerprint
        if fingerprint_dir.exists() and fingerprint_dir.is_dir():
            return fingerprint_dir
        else:
            logger.error(f"Directory for fingerprint {fingerprint} does not exist.")
            return None
