"""
EMU Discovery - Find and load EMU definitions from project directories.

This module handles discovering EMU definition files in project directories
following the convention:

    projects/
    ├── project_a/
    │   └── emus/
    │       ├── __init__.py
    │       ├── customer_support.py
    │       └── onboarding.py
    └── project_b/
        └── emus/
            └── sales_automation.py
"""
from __future__ import annotations

import importlib.util
import json
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


@dataclass
class DiscoveredEMU:
    """An EMU discovered from a project directory."""

    name: str
    definition: dict[str, Any]
    source_file: Path
    project: str

    def __repr__(self) -> str:
        return f"DiscoveredEMU(name={self.name!r}, project={self.project!r}, file={self.source_file.name})"


@dataclass
class DiscoveryResult:
    """Result of EMU discovery."""

    emus: list[DiscoveredEMU] = field(default_factory=list)
    errors: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)

    @property
    def success(self) -> bool:
        return len(self.errors) == 0

    def __len__(self) -> int:
        return len(self.emus)


def find_projects_dir(start_path: Path | None = None) -> Path | None:
    """
    Find the 'projects' directory by searching up from start_path.

    Args:
        start_path: Starting directory (defaults to current working directory)

    Returns:
        Path to projects directory if found, None otherwise
    """
    if start_path is None:
        start_path = Path.cwd()

    current = start_path.resolve()

    # Search up to 5 levels
    for _ in range(5):
        projects_dir = current / "projects"
        if projects_dir.is_dir():
            return projects_dir

        if current.parent == current:
            break
        current = current.parent

    return None


def discover_project_emus(project_path: Path) -> DiscoveryResult:
    """
    Discover all EMUs in a single project directory.

    Only discovers Python (.py) files automatically.
    JSON files must be explicitly specified via --file option.

    Args:
        project_path: Path to the project directory (e.g., projects/project_a)

    Returns:
        DiscoveryResult with discovered EMUs and any errors
    """
    result = DiscoveryResult()
    project_name = project_path.name

    emus_dir = project_path / "emus"
    if not emus_dir.is_dir():
        result.warnings.append(f"No 'emus' directory found in {project_path}")
        return result

    # Find all Python files in emus directory (excluding __init__.py)
    py_files = [
        f for f in emus_dir.glob("*.py")
        if f.name != "__init__.py" and not f.name.startswith("_")
    ]

    if not py_files:
        result.warnings.append(f"No EMU files found in {emus_dir}")
        return result

    # Load Python files only (JSON files require explicit --file)
    for py_file in py_files:
        file_emus, file_errors = load_emus_from_file(py_file, project_name)
        result.emus.extend(file_emus)
        result.errors.extend(file_errors)

    return result


def load_emus_from_file(file_path: Path, project_name: str) -> tuple[list[DiscoveredEMU], list[str]]:
    """
    Load EMU definitions from a Python file.

    EMUs can be defined in several ways:
    1. A module-level dict named 'EMU' or 'emu'
    2. A module-level list named 'EMUS' or 'emus'
    3. Any module-level dict with 'emu_key' and 'trigger' fields
    4. Functions that return EMU dicts

    Args:
        file_path: Path to the Python file
        project_name: Name of the project

    Returns:
        Tuple of (list of discovered EMUs, list of errors)
    """
    emus: list[DiscoveredEMU] = []
    errors: list[str] = []

    try:
        # Load the module dynamically
        module_name = f"_ami_emu_loader_{file_path.stem}"
        spec = importlib.util.spec_from_file_location(module_name, file_path)
        if spec is None or spec.loader is None:
            errors.append(f"Failed to load module spec from {file_path}")
            return emus, errors

        module = importlib.util.module_from_spec(spec)
        sys.modules[module_name] = module

        try:
            spec.loader.exec_module(module)
        except Exception as e:
            errors.append(f"Error executing {file_path}: {e}")
            return emus, errors

        # Look for EMU definitions
        # 1. Check for EMUS or emus list
        for list_name in ("EMUS", "emus", "EMU_LIST", "emu_list"):
            if hasattr(module, list_name):
                emu_list = getattr(module, list_name)
                if isinstance(emu_list, list):
                    for i, emu_def in enumerate(emu_list):
                        if is_emu_definition(emu_def):
                            name = emu_def.get("emu_key", f"emu_{i}")
                            emus.append(DiscoveredEMU(
                                name=name,
                                definition=emu_def,
                                source_file=file_path,
                                project=project_name,
                            ))

        # 2. Check for single EMU dict
        for dict_name in ("EMU", "emu"):
            if hasattr(module, dict_name):
                emu_def = getattr(module, dict_name)
                if is_emu_definition(emu_def):
                    name = emu_def.get("emu_key", dict_name)
                    emus.append(DiscoveredEMU(
                        name=name,
                        definition=emu_def,
                        source_file=file_path,
                        project=project_name,
                    ))

        # 3. Check all module-level dicts that look like EMUs
        for attr_name in dir(module):
            if attr_name.startswith("_"):
                continue
            if attr_name in ("EMUS", "emus", "EMU", "emu", "EMU_LIST", "emu_list"):
                continue

            attr = getattr(module, attr_name)
            if isinstance(attr, dict) and is_emu_definition(attr):
                name = attr.get("emu_key", attr_name)
                # Avoid duplicates
                if not any(e.name == name for e in emus):
                    emus.append(DiscoveredEMU(
                        name=name,
                        definition=attr,
                        source_file=file_path,
                        project=project_name,
                    ))

    except Exception as e:
        errors.append(f"Unexpected error loading {file_path}: {e}")

    finally:
        # Clean up the loaded module
        if module_name in sys.modules:
            del sys.modules[module_name]

    return emus, errors


def is_emu_definition(obj: Any) -> bool:
    """
    Check if an object looks like an EMU definition.

    An EMU definition must have at least 'emu_key' and 'trigger' fields.
    """
    if not isinstance(obj, dict):
        return False

    required_fields = {"emu_key", "trigger"}
    return required_fields.issubset(obj.keys())


def load_emus_from_json_file(file_path: Path, project_name: str) -> tuple[list[DiscoveredEMU], list[str]]:
    """
    Load EMU definitions from a JSON file.

    JSON files must have the structure: {"emus": [{...}, {...}]}

    Args:
        file_path: Path to the JSON file
        project_name: Name of the project

    Returns:
        Tuple of (list of discovered EMUs, list of errors)
    """
    emus: list[DiscoveredEMU] = []
    errors: list[str] = []

    try:
        with open(file_path, "r", encoding="utf-8") as f:
            data = json.load(f)

        # Validate structure
        if not isinstance(data, dict):
            errors.append(f"JSON file must be an object with 'emus' array: {file_path}")
            return emus, errors

        if "emus" not in data:
            errors.append(f"JSON file must have 'emus' key: {file_path}")
            return emus, errors

        if not isinstance(data["emus"], list):
            errors.append(f"'emus' must be an array: {file_path}")
            return emus, errors

        # Process each EMU
        for i, emu_def in enumerate(data["emus"]):
            if not isinstance(emu_def, dict):
                errors.append(f"EMU at index {i} must be an object: {file_path}")
                continue

            if not is_emu_definition(emu_def):
                errors.append(f"EMU at index {i} missing required fields (emu_key, trigger): {file_path}")
                continue

            name = emu_def.get("emu_key", f"emu_{i}")
            emus.append(DiscoveredEMU(
                name=name,
                definition=emu_def,
                source_file=file_path,
                project=project_name,
            ))

    except json.JSONDecodeError as e:
        errors.append(f"Invalid JSON in {file_path}: {e}")
    except Exception as e:
        errors.append(f"Error reading {file_path}: {e}")

    return emus, errors


def discover_all_emus(projects_dir: Path | None = None) -> DiscoveryResult:
    """
    Discover all EMUs across all projects.

    Args:
        projects_dir: Path to the projects directory (auto-detected if not provided)

    Returns:
        DiscoveryResult with all discovered EMUs and any errors
    """
    if projects_dir is None:
        projects_dir = find_projects_dir()
        if projects_dir is None:
            return DiscoveryResult(errors=["Could not find 'projects' directory"])

    result = DiscoveryResult()

    if not projects_dir.is_dir():
        result.errors.append(f"Projects directory does not exist: {projects_dir}")
        return result

    # Find all project directories (directories with an 'emus' subdirectory)
    project_dirs = [
        d for d in projects_dir.iterdir()
        if d.is_dir() and (d / "emus").is_dir()
    ]

    if not project_dirs:
        result.warnings.append(f"No projects with 'emus' directories found in {projects_dir}")
        return result

    for project_dir in sorted(project_dirs):
        project_result = discover_project_emus(project_dir)
        result.emus.extend(project_result.emus)
        result.errors.extend(project_result.errors)
        result.warnings.extend(project_result.warnings)

    return result
