"""
Library management for KiCAD footprints

Handles parsing fp-lib-table files, discovering footprints,
and providing search functionality for component placement.
"""

import glob
import logging
import os
import re
from pathlib import Path
from typing import Dict, List, Optional, Tuple

from utils.kicad_roots import kicad_install_roots
from utils.platform_helper import PlatformHelper

logger = logging.getLogger("kicad_interface")


class LibraryManager:
    """
    Manages KiCAD footprint libraries

    Parses fp-lib-table files (both global and project-specific),
    indexes available footprints, and provides search functionality.
    """

    def __init__(self, project_path: Optional[Path] = None):
        """
        Initialize library manager

        Args:
            project_path: Optional path to project directory for project-specific libraries
        """
        self.project_path = project_path
        self.libraries: Dict[str, str] = {}  # nickname -> path mapping
        self.footprint_cache: Dict[str, List[str]] = {}  # library -> [footprint names]
        self._load_libraries()

    def _load_libraries(self) -> None:
        """Load libraries from fp-lib-table files"""
        # Load global libraries
        global_table = self._get_global_fp_lib_table()
        if global_table and global_table.exists():
            logger.info(f"Loading global fp-lib-table from: {global_table}")
            self._parse_fp_lib_table(global_table)
        else:
            logger.warning(f"Global fp-lib-table not found at: {global_table}")

        # Load project-specific libraries if project path provided
        if self.project_path:
            project_table = self.project_path / "fp-lib-table"
            if project_table.exists():
                logger.info(f"Loading project fp-lib-table from: {project_table}")
                self._parse_fp_lib_table(project_table)

        logger.info(f"Loaded {len(self.libraries)} footprint libraries")

    def _get_global_fp_lib_table(self) -> Optional[Path]:
        """Get path to global fp-lib-table file"""
        # Try different possible locations
        kicad_config_paths = [
            Path.home() / ".config" / "kicad" / "10.0" / "fp-lib-table",
            Path.home() / ".config" / "kicad" / "9.0" / "fp-lib-table",
            Path.home() / ".config" / "kicad" / "8.0" / "fp-lib-table",
            Path.home() / ".config" / "kicad" / "fp-lib-table",
            # Windows paths
            Path.home() / "AppData" / "Roaming" / "kicad" / "10.0" / "fp-lib-table",
            Path.home() / "AppData" / "Roaming" / "kicad" / "9.0" / "fp-lib-table",
            Path.home() / "AppData" / "Roaming" / "kicad" / "8.0" / "fp-lib-table",
            # macOS paths
            Path.home() / "Library" / "Preferences" / "kicad" / "10.0" / "fp-lib-table",
            Path.home() / "Library" / "Preferences" / "kicad" / "9.0" / "fp-lib-table",
            Path.home() / "Library" / "Preferences" / "kicad" / "8.0" / "fp-lib-table",
        ]

        for path in kicad_config_paths:
            if path.exists():
                return path

        return None

    def _parse_fp_lib_table(self, table_path: Path) -> None:
        """
        Parse fp-lib-table file

        Format is S-expression (Lisp-like):
        (fp_lib_table
          (lib (name "Library_Name")(type KiCad)(uri "${KICAD9_FOOTPRINT_DIR}/Library.pretty")(options "")(descr "Description"))
        )
        """
        try:
            with open(table_path, "r") as f:
                content = f.read()

            # Simple regex-based parser for lib entries. Name, type, and URI
            # may be quoted or bare; quoted URIs can contain spaces.
            lib_pattern = (
                r"\(lib\s+"
                r'\(name\s+(?:"([^"]+)"|([^"\)\s]+))\)\s*'
                r'\(type\s+(?:"([^"]+)"|([^"\)\s]+))\)\s*'
                r'\(uri\s+(?:"([^"]+)"|([^"\)\s]+))'
            )

            for match in re.finditer(lib_pattern, content, re.IGNORECASE):
                nickname = match.group(1) or match.group(2)
                lib_type = match.group(3) or match.group(4)
                uri = match.group(5) or match.group(6)

                if lib_type.lower() == "table":
                    table_uri = uri
                    if os.path.isabs(table_uri) and os.path.isfile(table_uri):
                        logger.info(f"  Following Table reference: {nickname} -> {table_uri}")
                        self._parse_fp_lib_table(Path(table_uri))
                    else:
                        logger.warning(f"  Could not resolve Table URI: {table_uri}")
                    continue

                # Resolve environment variables in URI
                resolved_uri = self._resolve_uri(uri)

                if resolved_uri:
                    self.libraries[nickname] = resolved_uri
                    logger.debug(f"  Found library: {nickname} -> {resolved_uri}")
                else:
                    logger.warning(f"  Could not resolve URI for library {nickname}: {uri}")

        except Exception as e:
            logger.error(f"Error parsing fp-lib-table at {table_path}: {e}")

    def _resolve_uri(self, uri: str) -> Optional[str]:
        """
        Resolve environment variables and paths in library URI

        Handles:
        - ${KICAD9_FOOTPRINT_DIR} -> /usr/share/kicad/footprints
        - ${KICAD8_FOOTPRINT_DIR} -> /usr/share/kicad/footprints
        - ${KIPRJMOD} -> project directory
        - Relative paths
        - Absolute paths
        """
        # Replace environment variables
        resolved = uri

        # Common KiCAD environment variables
        env_vars = {
            "KICAD10_FOOTPRINT_DIR": self._find_kicad_footprint_dir(),
            "KICAD9_FOOTPRINT_DIR": self._find_kicad_footprint_dir(),
            "KICAD8_FOOTPRINT_DIR": self._find_kicad_footprint_dir(),
            "KICAD_FOOTPRINT_DIR": self._find_kicad_footprint_dir(),
            "KISYSMOD": self._find_kicad_footprint_dir(),
            "KICAD10_3RD_PARTY": self._find_kicad_3rdparty_dir(),
            "KICAD9_3RD_PARTY": self._find_kicad_3rdparty_dir(),
            "KICAD8_3RD_PARTY": self._find_kicad_3rdparty_dir(),
            "KICAD_3RD_PARTY": self._find_kicad_3rdparty_dir(),
        }

        # Merge user-defined env vars from kicad_common.json
        env_vars.update(PlatformHelper.load_kicad_env_vars())

        # Project directory
        if self.project_path:
            env_vars["KIPRJMOD"] = str(self.project_path)

        # Replace environment variables
        for var, value in env_vars.items():
            if value:
                resolved = resolved.replace(f"${{{var}}}", value)
                resolved = resolved.replace(f"${var}", value)

        # Expand ~ to home directory
        resolved = os.path.expanduser(resolved)

        # Convert to absolute path
        path = Path(resolved)

        # Check if path exists
        if path.exists():
            return str(path)
        else:
            logger.debug(f"    Path does not exist: {path}")
            return None

    def _find_kicad_footprint_dir(self) -> Optional[str]:
        """Find KiCAD footprint directory"""
        # Try common locations
        possible_paths = [
            "/usr/share/kicad/footprints",
            "/usr/local/share/kicad/footprints",
            "/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints",
        ]

        # Prepend Windows install roots from the shared discovery helper (registry
        # + Program Files + custom C:\KiCad roots, newest first) so a relocated
        # install's footprints are found, not just Program Files ones (#286).
        for root in reversed(kicad_install_roots()):
            possible_paths.insert(0, str(root / "share" / "kicad" / "footprints"))

        # Also check environment variable
        if "KICAD10_FOOTPRINT_DIR" in os.environ:
            possible_paths.insert(0, os.environ["KICAD10_FOOTPRINT_DIR"])
        if "KICAD9_FOOTPRINT_DIR" in os.environ:
            possible_paths.insert(0, os.environ["KICAD9_FOOTPRINT_DIR"])
        if "KICAD8_FOOTPRINT_DIR" in os.environ:
            possible_paths.insert(0, os.environ["KICAD8_FOOTPRINT_DIR"])

        for path in possible_paths:
            if os.path.isdir(path):
                return path

        return None

    def _find_kicad_3rdparty_dir(self) -> Optional[str]:
        """
        Find KiCAD 3rd party libraries directory.

        Resolution order:
        1. Shell environment variable KICAD9_3RD_PARTY
        2. User settings in kicad_common.json
        3. Platform-specific defaults based on detected KiCad version
        """
        import json

        # 1. Check shell environment variable first
        for var in ("KICAD10_3RD_PARTY", "KICAD9_3RD_PARTY", "KICAD8_3RD_PARTY", "KICAD_3RD_PARTY"):
            if var in os.environ:
                path = os.environ[var]
                if os.path.isdir(path):
                    return path

        # 2. Check kicad_common.json for user-defined variables
        kicad_common_paths = [
            Path.home()
            / "Library"
            / "Preferences"
            / "kicad"
            / "9.0"
            / "kicad_common.json",  # macOS
            Path.home() / ".config" / "kicad" / "9.0" / "kicad_common.json",  # Linux
            Path.home() / "AppData" / "Roaming" / "kicad" / "9.0" / "kicad_common.json",  # Windows
        ]

        for config_path in kicad_common_paths:
            if config_path.exists():
                try:
                    with open(config_path, "r") as f:
                        config = json.load(f)
                    env_vars = config.get("environment", {}).get("vars", {})
                    if env_vars and "KICAD9_3RD_PARTY" in env_vars:
                        path = env_vars["KICAD9_3RD_PARTY"]
                        if os.path.isdir(path):
                            return path
                except (json.JSONDecodeError, KeyError, TypeError):
                    pass

                # Derive version from config path location
                version = config_path.parent.name  # e.g., "9.0"
                break
        else:
            version = "9.0"  # Default

        # 3. Use platform-specific defaults
        possible_paths = [
            # macOS - Documents/KiCad/{version}/3rdparty
            Path.home() / "Documents" / "KiCad" / version / "3rdparty",
            # Linux - ~/.local/share/kicad/{version}/3rdparty
            Path.home() / ".local" / "share" / "kicad" / version / "3rdparty",
            # Windows - Documents/KiCad/{version}/3rdparty
            Path.home() / "Documents" / "KiCad" / version / "3rdparty",
        ]

        for candidate in possible_paths:
            if candidate.exists():
                logger.info(f"Found KiCad 3rd party directory: {candidate}")
                return str(candidate)

        logger.warning("Could not find KiCad 3rd party directory")
        return None

    def list_libraries(self) -> List[str]:
        """Get list of available library nicknames"""
        return list(self.libraries.keys())

    def get_library_path(self, nickname: str) -> Optional[str]:
        """Get filesystem path for a library nickname"""
        return self.libraries.get(nickname)

    def list_footprints(self, library_nickname: str) -> List[str]:
        """
        List all footprints in a library

        Args:
            library_nickname: Library name (e.g., "Resistor_SMD")

        Returns:
            List of footprint names (without .kicad_mod extension)
        """
        # Check cache first
        if library_nickname in self.footprint_cache:
            return self.footprint_cache[library_nickname]

        library_path = self.libraries.get(library_nickname)
        if not library_path:
            logger.warning(f"Library not found: {library_nickname}")
            return []

        try:
            footprints = []
            lib_dir = Path(library_path)

            # List all .kicad_mod files
            for fp_file in lib_dir.glob("*.kicad_mod"):
                # Remove .kicad_mod extension
                footprint_name = fp_file.stem
                footprints.append(footprint_name)

            # Cache the results
            self.footprint_cache[library_nickname] = footprints
            logger.debug(f"Found {len(footprints)} footprints in {library_nickname}")

            return footprints

        except Exception as e:
            logger.error(f"Error listing footprints in {library_nickname}: {e}")
            return []

    def find_footprint(self, footprint_spec: str) -> Optional[Tuple[str, str]]:
        """
        Find a footprint by specification

        Supports multiple formats:
        - "Library:Footprint" (e.g., "Resistor_SMD:R_0603_1608Metric")
        - "Footprint" (searches all libraries)

        Args:
            footprint_spec: Footprint specification

        Returns:
            Tuple of (library_path, footprint_name) or None if not found
        """
        # Parse specification
        if ":" in footprint_spec:
            # Format: Library:Footprint
            library_nickname, footprint_name = footprint_spec.split(":", 1)
            library_path = self.libraries.get(library_nickname)

            if not library_path:
                logger.warning(f"Library not found: {library_nickname}")
                return None

            # Check if footprint exists
            fp_file = Path(library_path) / f"{footprint_name}.kicad_mod"
            if fp_file.exists():
                return (library_path, footprint_name)
            else:
                logger.warning(f"Footprint not found: {footprint_spec}")
                return None
        else:
            # Format: Footprint (search all libraries)
            footprint_name = footprint_spec

            # Search in all libraries
            for library_nickname, library_path in self.libraries.items():
                fp_file = Path(library_path) / f"{footprint_name}.kicad_mod"
                if fp_file.exists():
                    logger.info(f"Found footprint {footprint_name} in library {library_nickname}")
                    return (library_path, footprint_name)

            logger.warning(f"Footprint not found in any library: {footprint_name}")
            return None

    def search_footprints(self, pattern: str, limit: int = 20) -> List[Dict[str, str]]:
        """
        Search for footprints matching a pattern

        Args:
            pattern: Search pattern (supports wildcards *, case-insensitive)
            limit: Maximum number of results to return

        Returns:
            List of dicts with 'library', 'footprint', and 'full_name' keys
        """
        results = []
        pattern_lower = pattern.lower()

        # Convert wildcards to regex
        regex_pattern = pattern_lower.replace("*", ".*")
        regex = re.compile(regex_pattern)

        for library_nickname in self.libraries.keys():
            footprints = self.list_footprints(library_nickname)

            for footprint in footprints:
                if regex.search(footprint.lower()):
                    results.append(
                        {
                            "library": library_nickname,
                            "footprint": footprint,
                            "full_name": f"{library_nickname}:{footprint}",
                        }
                    )

                    if len(results) >= limit:
                        return results

        return results

    def get_footprint_info(
        self, library_nickname: str, footprint_name: str
    ) -> Optional[Dict[str, str]]:
        """
        Get information about a specific footprint

        Args:
            library_nickname: Library name
            footprint_name: Footprint name

        Returns:
            Dict with footprint information or None if not found
        """
        library_path = self.libraries.get(library_nickname)
        if not library_path:
            return None

        fp_file = Path(library_path) / f"{footprint_name}.kicad_mod"
        if not fp_file.exists():
            return None

        return {
            "library": library_nickname,
            "footprint": footprint_name,
            "full_name": f"{library_nickname}:{footprint_name}",
            "path": str(fp_file),
            "library_path": library_path,
        }


class LibraryCommands:
    """Command handlers for library operations"""

    def __init__(self, library_manager: Optional[LibraryManager] = None):
        """Initialize with optional library manager"""
        self.library_manager = library_manager or LibraryManager()

    def list_libraries(self, params: Dict) -> Dict:
        """List all available footprint libraries"""
        try:
            libraries = self.library_manager.list_libraries()
            return {"success": True, "libraries": libraries, "count": len(libraries)}
        except Exception as e:
            logger.error(f"Error listing libraries: {e}")
            return {
                "success": False,
                "message": "Failed to list libraries",
                "errorDetails": str(e),
            }

    def search_footprints(self, params: Dict) -> Dict:
        """Search for footprints by pattern"""
        try:
            # Support both 'pattern' and 'search_term' parameter names
            pattern = params.get("pattern") or params.get("search_term", "*")
            limit = params.get("limit", 20)
            library_filter = params.get("library")

            results = self.library_manager.search_footprints(
                pattern, limit * 10 if library_filter else limit
            )

            # Filter by library if specified
            if library_filter:
                results = [
                    r for r in results if r.get("library", "").lower() == library_filter.lower()
                ]
                results = results[:limit]

            return {
                "success": True,
                "footprints": results,
                "count": len(results),
                "pattern": pattern,
            }
        except Exception as e:
            logger.error(f"Error searching footprints: {e}")
            return {
                "success": False,
                "message": "Failed to search footprints",
                "errorDetails": str(e),
            }

    def list_library_footprints(self, params: Dict) -> Dict:
        """List all footprints in a specific library"""
        try:
            library = params.get("library") or params.get("library_name")
            if not library:
                return {"success": False, "message": "Missing library parameter"}

            footprints = self.library_manager.list_footprints(library)

            return {
                "success": True,
                "library": library,
                "footprints": footprints,
                "count": len(footprints),
            }
        except Exception as e:
            logger.error(f"Error listing library footprints: {e}")
            return {
                "success": False,
                "message": "Failed to list library footprints",
                "errorDetails": str(e),
            }

    def get_footprint_info(self, params: Dict) -> Dict:
        """Get information about a specific footprint"""
        try:
            footprint_spec = params.get("footprint_name")
            if not footprint_spec:
                return {"success": False, "message": "Missing footprint parameter"}

            # Try to find the footprint
            result = self.library_manager.find_footprint(footprint_spec)

            if result:
                library_path, footprint_name = result
                # Extract library nickname from path
                library_nickname = None
                for nick, path in self.library_manager.libraries.items():
                    if path == library_path:
                        library_nickname = nick
                        break

            # Minimal info — always returned even if the parser fails
            info: Dict = {
                "library": library_nickname,
                "name": footprint_name,
                "full_name": f"{library_nickname}:{footprint_name}",
                "library_path": library_path,
            }

            # Attempt to enrich with parsed .kicad_mod data
            try:
                from pathlib import Path as _Path

                from parsers.kicad_mod_parser import parse_kicad_mod

                mod_file = str(_Path(library_path) / f"{footprint_name}.kicad_mod")
                parsed = parse_kicad_mod(mod_file)
                if parsed:
                    # Merge parser output into info; keep our resolved library context
                    info.update(parsed)
                    info["name"] = footprint_name  # entry name wins over in-file name
                    info["library"] = library_nickname
                    info["full_name"] = f"{library_nickname}:{footprint_name}"
                    info["library_path"] = library_path
                else:
                    logger.warning(
                        f"get_footprint_info: parser returned nothing for {mod_file}, using minimal info"
                    )
            except Exception as parse_err:
                logger.warning(
                    f"get_footprint_info: parser error ({parse_err}), using minimal info"
                )

            return {"success": True, "info": info}

        except Exception as e:
            logger.error(f"Error getting footprint info: {e}")
            return {
                "success": False,
                "message": "Failed to get footprint info",
                "errorDetails": str(e),
            }
