"""Path resolution helpers.

This module contains functions for resolving Ontos directory and file paths
with mode-awareness (contributor vs user mode) and backward compatibility.

IMPURE: Many functions use os.path.exists() and imports from config modules.
"""

import os
import importlib
import importlib.util
import warnings
from functools import lru_cache
from datetime import datetime
from pathlib import Path
from typing import Optional, Union


# NOTE:
# Keep PROJECT_ROOT constant for compatibility with legacy imports, but do not
# use it as the runtime anchor for path resolution.
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


def _normalize_start_path(start_path: Optional[Union[Path, str]] = None) -> str:
    """Normalize root-discovery start path for cache keys."""
    if start_path is None:
        return str(Path.cwd().resolve())
    return str(Path(start_path).expanduser().resolve())


@lru_cache(maxsize=128)
def _discover_runtime_root_cached(start_path: str) -> str:
    """Discover project root using runtime precedence from a normalized start path."""
    current = Path(start_path)
    while True:
        if (current / ".ontos.toml").exists():
            return str(current)
        if (current / ".ontos").exists() or (current / ".ontos-internal").exists():
            return str(current)
        if (current / ".git").exists():
            return str(current)
        if current.parent == current:
            break
        current = current.parent

    raise FileNotFoundError(
        "Not in an Ontos project. No .ontos.toml, .ontos/.ontos-internal, or .git found."
    )


def resolve_project_root(
    repo_root: Optional[Union[Path, str]] = None,
    *,
    start_path: Optional[Union[Path, str]] = None,
) -> Path:
    """Resolve runtime project root.

    If repo_root is provided, it is used directly. Otherwise, root is discovered
    from start_path/cwd using cached precedence-based lookup.
    """
    if repo_root is not None:
        return Path(repo_root).expanduser().resolve()
    return Path(_discover_runtime_root_cached(_normalize_start_path(start_path)))


def is_ontos_repo(repo_root: Optional[Union[Path, str]] = None) -> bool:
    """Check if this is the Ontos repository itself (contributor mode).

    Returns:
        True if .ontos-internal/ exists (contributor mode),
        False otherwise (user mode).
    """
    root = resolve_project_root(repo_root=repo_root)
    return (root / ".ontos-internal").exists()


def resolve_config(setting_name: str, default=None, warn_legacy: bool = True):
    """Resolve a config value with legacy compatibility.

    Resolution order:
    1. ``ontos_config.py`` override (if present on PYTHONPATH)
    2. ``ontos_config_defaults.py`` fallback (if present on PYTHONPATH)
    3. Provided ``default`` argument
    """
    if importlib.util.find_spec("ontos_config") is not None:
        try:
            user_config = importlib.import_module("ontos_config")
        except Exception as exc:
            if warn_legacy:
                _warn_legacy_config_import_failure("ontos_config.py", exc)
        else:
            if warn_legacy:
                _warn_legacy_config()
            if hasattr(user_config, setting_name):
                return getattr(user_config, setting_name)

    if importlib.util.find_spec("ontos_config_defaults") is not None:
        try:
            defaults_module = importlib.import_module("ontos_config_defaults")
        except Exception as exc:
            if warn_legacy:
                _warn_legacy_config_import_failure("ontos_config_defaults.py", exc)
        else:
            if hasattr(defaults_module, setting_name):
                return getattr(defaults_module, setting_name)

    return default


_legacy_config_warning_emitted = False
_legacy_config_import_failure_warned = set()


def _warn_legacy_config() -> None:
    """Warn once when legacy ontos_config.py is detected."""
    global _legacy_config_warning_emitted
    if _legacy_config_warning_emitted:
        return
    _legacy_config_warning_emitted = True
    warnings.warn(
        "Legacy ontos_config.py detected; this path is deprecated and will be removed "
        "in a future release. Use .ontos.toml.",
        FutureWarning,
        stacklevel=3,
    )


def _warn_legacy_config_import_failure(module_label: str, exc: Exception) -> None:
    """Warn once when a legacy config module exists but cannot be imported."""
    if module_label in _legacy_config_import_failure_warned:
        return
    _legacy_config_import_failure_warned.add(module_label)
    warnings.warn(
        f"Failed to load legacy {module_label} ({exc.__class__.__name__}: {exc}). "
        "Ignoring legacy config and falling back to defaults. Use .ontos.toml.",
        FutureWarning,
        stacklevel=3,
    )


# Track deprecation warnings to avoid spam
_deprecation_warned = set()


def _warn_deprecated(old_path: str, new_path: str) -> None:
    """Issue deprecation warning using Python's warnings system.

    Uses stdlib warnings module for proper filtering, deduplication,
    and CLI suppression. No caller changes required.
    """
    if old_path in _deprecation_warned:
        return
    _deprecation_warned.add(old_path)
    warnings.warn(
        f"Using deprecated path '{old_path}'. "
        f"Expected: '{new_path}'. "
        f"Run 'python3 ontos_init.py' to update.",
        FutureWarning,
        stacklevel=3  # Point to caller's caller
    )


def _runtime_docs_and_logs_dirs(root: Path) -> tuple[str, Optional[str]]:
    """Resolve docs/logs dirs from runtime project config with legacy fallback."""
    try:
        from ontos.io.config import load_project_config

        config = load_project_config(repo_root=root)
        docs_dir = str(config.paths.docs_dir).strip() or "docs"
        logs_dir = str(config.paths.logs_dir).strip() if getattr(config.paths, "logs_dir", None) else None
        return docs_dir, logs_dir
    except Exception:
        docs_dir = str(resolve_config("DOCS_DIR", "docs")).strip()
        logs_dir_raw = resolve_config("LOGS_DIR", None)
        logs_dir = str(logs_dir_raw).strip() if logs_dir_raw else None
        return docs_dir, logs_dir


def get_logs_dir(repo_root: Optional[Union[Path, str]] = None) -> str:
    """Get the logs directory path based on config.

    Respects LOGS_DIR config setting if set, otherwise derives from DOCS_DIR.
    Handles both contributor mode (.ontos-internal) and user mode (docs/logs).

    Returns:
        Absolute path to logs directory.
    """
    root = resolve_project_root(repo_root=repo_root)

    # Contributor mode uses .ontos-internal/logs
    if is_ontos_repo(repo_root=root):
        return str(root / ".ontos-internal" / "logs")

    docs_dir, logs_dir = _runtime_docs_and_logs_dirs(root)

    # Try LOGS_DIR first (most explicit)
    if logs_dir and os.path.isabs(logs_dir):
        return logs_dir

    # User mode: derive from LOGS_DIR or DOCS_DIR
    if logs_dir:
        return str(root / logs_dir)

    return str(root / docs_dir / "logs")


def get_log_count(repo_root: Optional[Union[Path, str]] = None) -> int:
    """Count active session logs in logs directory.

    Only counts markdown files starting with a digit (date-prefixed logs).

    Returns:
        Number of active log files.
    """
    logs_dir = get_logs_dir(repo_root=repo_root)
    if not os.path.exists(logs_dir):
        return 0

    return len([f for f in os.listdir(logs_dir)
                if f.endswith('.md') and f[0].isdigit()])


def get_logs_older_than(days: int, repo_root: Optional[Union[Path, str]] = None) -> list:
    """Get list of log filenames older than N days.

    Args:
        days: Age threshold in days.

    Returns:
        List of log filenames (not full paths) older than threshold.
    """
    from datetime import timedelta
    logs_dir = get_logs_dir(repo_root=repo_root)
    if not os.path.exists(logs_dir):
        return []

    cutoff = datetime.now() - timedelta(days=days)
    old_logs = []

    for filename in os.listdir(logs_dir):
        if not filename.endswith('.md') or not filename[0].isdigit():
            continue
        try:
            log_date = datetime.strptime(filename[:10], '%Y-%m-%d')
            if log_date < cutoff:
                old_logs.append(filename)
        except ValueError:
            continue

    return old_logs


def get_archive_dir(repo_root: Optional[Union[Path, str]] = None) -> str:
    """Get the archive directory path based on config.

    Returns:
        Absolute path to archive directory.
    """
    root = resolve_project_root(repo_root=repo_root)

    if is_ontos_repo(repo_root=root):
        return str(root / ".ontos-internal" / "archive")

    docs_dir, _ = _runtime_docs_and_logs_dirs(root)
    return str(root / docs_dir / "archive")


def get_decision_history_path(repo_root: Optional[Union[Path, str]] = None) -> str:
    """Get decision_history.md path with backward compatibility.

    v2.5.2: Uses nested structure (docs/strategy/decision_history.md)
    Pre-v2.5.2: Falls back to flat structure (docs/decision_history.md)

    Returns:
        Absolute path to decision_history.md.
    """
    root = resolve_project_root(repo_root=repo_root)

    if is_ontos_repo(repo_root=root):
        return str(root / ".ontos-internal" / "reference" / "decision_history.md")

    docs_dir, _ = _runtime_docs_and_logs_dirs(root)

    # Try new location first (v2.5.2+)
    new_path = str(root / docs_dir / "strategy" / "decision_history.md")
    if os.path.exists(new_path):
        return new_path

    # Fall back to old location (pre-v2.5.2)
    old_path = str(root / docs_dir / "decision_history.md")
    if os.path.exists(old_path):
        _warn_deprecated(f'{docs_dir}/decision_history.md', f'{docs_dir}/strategy/decision_history.md')
        return old_path

    # Return new location for creation
    return new_path


def get_proposals_dir(repo_root: Optional[Union[Path, str]] = None) -> str:
    """Get proposals directory path (mode-aware).

    Returns:
        Absolute path to proposals directory.
    """
    root = resolve_project_root(repo_root=repo_root)

    if is_ontos_repo(repo_root=root):
        return str(root / ".ontos-internal" / "strategy" / "proposals")

    docs_dir, _ = _runtime_docs_and_logs_dirs(root)
    return str(root / docs_dir / "strategy" / "proposals")


def get_archive_logs_dir(repo_root: Optional[Union[Path, str]] = None) -> str:
    """Get archive/logs directory path with backward compatibility.

    v2.5.2: Uses nested structure (docs/archive/logs/)
    Pre-v2.5.2: Falls back to flat structure (docs/archive/)

    Returns:
        Absolute path to archive/logs directory.
    """
    root = resolve_project_root(repo_root=repo_root)

    if is_ontos_repo(repo_root=root):
        return str(root / ".ontos-internal" / "archive" / "logs")

    docs_dir, _ = _runtime_docs_and_logs_dirs(root)

    # Try new location first (v2.5.2+)
    new_path = str(root / docs_dir / "archive" / "logs")
    if os.path.exists(new_path):
        return new_path

    # Fall back to old location (pre-v2.5.2: logs directly in archive/)
    old_path = str(root / docs_dir / "archive")
    if os.path.exists(old_path):
        _warn_deprecated(f'{docs_dir}/archive/', f'{docs_dir}/archive/logs/')
        return old_path

    # Return new location for creation
    return new_path


def get_archive_proposals_dir(repo_root: Optional[Union[Path, str]] = None) -> str:
    """Get archive/proposals directory path (mode-aware).

    New in v2.5.2 - no backward compatibility needed.

    Returns:
        Absolute path to archive/proposals directory.
    """
    root = resolve_project_root(repo_root=repo_root)

    if is_ontos_repo(repo_root=root):
        return str(root / ".ontos-internal" / "archive" / "proposals")

    docs_dir, _ = _runtime_docs_and_logs_dirs(root)
    return str(root / docs_dir / "archive" / "proposals")


def get_concepts_path(repo_root: Optional[Union[Path, str]] = None) -> str:
    """Get Common_Concepts.md path with backward compatibility.

    v2.5.2: Uses nested structure (docs/reference/Common_Concepts.md)
    Pre-v2.5.2: Falls back to flat structure (docs/Common_Concepts.md)

    Returns:
        Absolute path to Common_Concepts.md.
    """
    root = resolve_project_root(repo_root=repo_root)

    if is_ontos_repo(repo_root=root):
        return str(root / ".ontos-internal" / "reference" / "Common_Concepts.md")

    docs_dir, _ = _runtime_docs_and_logs_dirs(root)

    # Try new location first (v2.5.2+)
    new_path = str(root / docs_dir / "reference" / "Common_Concepts.md")
    if os.path.exists(new_path):
        return new_path

    # Fall back to old location (pre-v2.5.2)
    old_path = str(root / docs_dir / "Common_Concepts.md")
    if os.path.exists(old_path):
        _warn_deprecated(f'{docs_dir}/Common_Concepts.md', f'{docs_dir}/reference/Common_Concepts.md')
        return old_path

    # Return new location for creation
    return new_path


def find_last_session_date(
    logs_dir: Optional[str] = None,
    repo_root: Optional[Union[Path, str]] = None,
) -> str:
    """Find the date of the most recent session log.

    Args:
        logs_dir: Directory containing log files. If None, uses LOGS_DIR from config.

    Returns:
        Date string in YYYY-MM-DD format, or empty string if no logs found.
    """
    if logs_dir is None:
        logs_dir = get_logs_dir(repo_root=repo_root)

    if not os.path.exists(logs_dir):
        return ""

    log_files = []
    for filename in os.listdir(logs_dir):
        if filename.endswith('.md') and len(filename) >= 10:
            date_part = filename[:10]
            if date_part.count('-') == 2:
                log_files.append(date_part)

    if not log_files:
        return ""

    return sorted(log_files)[-1]
