"""GnommoCache - External storage extension for large media files.

Provides transparent fallback to external storage when files are not found locally.
Configure via ~/.gnommo.conf:

    [cache]
    path = /Volumes/GnommoDisk/gnommo

Files are looked up first locally, then in the cache at:
    {cache_path}/{project_name}/{relative_path}
"""

import configparser
import os
from pathlib import Path
from typing import Optional, Tuple

_cache_config: Optional[dict] = None
_assets_config: Optional[dict] = None
_perf_config: Optional[dict] = None


def get_ffmpeg_thread_count() -> int:
    """Return FFmpeg thread count based on [performance] cpu_limit in ~/.gnommo.conf.

    cpu_limit is a fraction of logical CPUs (e.g. 0.8 = 80%).
    Defaults to 1 when not configured, which is safe on memory-constrained machines.

    Example ~/.gnommo.conf:
        [performance]
        cpu_limit = 0.8
    """
    global _perf_config
    if _perf_config is None:
        config_path = Path.home() / ".gnommo.conf"
        _perf_config = {}
        if config_path.exists():
            cfg = configparser.ConfigParser()
            cfg.read(config_path)
            if cfg.has_option("performance", "cpu_limit"):
                try:
                    _perf_config["cpu_limit"] = float(
                        cfg.get("performance", "cpu_limit")
                    )
                except ValueError:
                    pass

    cpu_limit = _perf_config.get("cpu_limit")
    if cpu_limit is None:
        return 1
    cpu_count = os.cpu_count() or 1
    return max(1, int(cpu_count * cpu_limit))


def get_render_chunk_size() -> Optional[int]:
    """Return slides-per-chunk for auto-chunked rendering, or None if not configured.

    When set, cmd_render splits the filter graph into chunks of this many slides
    to avoid OOM from allocating filter buffers for the entire video at once.

    Example ~/.gnommo.conf:
        [performance]
        render_chunk_slides = 15
    """
    global _perf_config
    if _perf_config is None:
        get_ffmpeg_thread_count()  # populates _perf_config
    val = _perf_config.get("render_chunk_slides")
    if val is None:
        return None
    try:
        return max(1, int(val))
    except (ValueError, TypeError):
        return None


def load_cache_config() -> Optional[Path]:
    """Load gnommo.conf and return cache path if configured.

    Configuration file location: ~/.gnommo.conf

    Returns:
        Path to the cache root directory, or None if not configured.
    """
    global _cache_config
    if _cache_config is not None:
        return _cache_config.get("path")

    config_path = Path.home() / ".gnommo.conf"
    if not config_path.exists():
        _cache_config = {}
        return None

    config = configparser.ConfigParser()
    config.read(config_path)

    if config.has_option("cache", "path"):
        cache_path = Path(config.get("cache", "path"))
        _cache_config = {"path": cache_path}
        return cache_path

    _cache_config = {}
    return None


def load_assets_process_cache() -> Optional[Path]:
    """Return the process-cache path on the [assets] disk, or None if not configured.

    Derived by replacing the last component of the [assets] path with
    that name + "cache".  E.g.:
        [assets] path = /Volumes/LaCie Jens/Projects/gnommo
        → process cache  = /Volumes/LaCie Jens/Projects/gnommocache

    This mirrors the GnommoDisk convention where the asset root is
    /Volumes/GnommoDisk/gnommo and the process cache is /Volumes/GnommoDisk/gnommocache.
    """
    assets_path = load_assets_config()
    if assets_path is None:
        return None
    return assets_path.parent / (assets_path.name + "cache")


def load_assets_config() -> Optional[Path]:
    """Load gnommo.conf and return the [assets] path if configured.

    The assets path is a second external fallback (e.g. a LaCie drive) with
    the same directory layout as the gnommo project root.  Resolution order is:
    local → cache ([cache] path) → assets ([assets] path).

    Example ~/.gnommo.conf:
        [assets]
        path = /Volumes/LaCie Jens/Projects/gnommo
    """
    global _assets_config
    if _assets_config is not None:
        return _assets_config.get("path")

    config_path = Path.home() / ".gnommo.conf"
    if not config_path.exists():
        _assets_config = {}
        return None

    config = configparser.ConfigParser()
    config.read(config_path)

    if config.has_option("assets", "path"):
        assets_path = Path(config.get("assets", "path"))
        _assets_config = {"path": assets_path}
        return assets_path

    _assets_config = {}
    return None


def _resolve_against_base(
    local_path: Path, project_path: Path, base: Path
) -> Optional[Path]:
    """Try to find local_path mirrored under base.

    Tries two mappings:
      1. project-relative:  base / project_name / relative_to_project
      2. gnommo-root-relative:  base / relative_to_project_parent  (e.g. shared_assets/…)
    """
    try:
        relative = local_path.relative_to(project_path)
        p = base / project_path.name / relative
        if p.exists():
            return p
    except ValueError:
        pass

    try:
        relative = local_path.relative_to(project_path.parent)
        p = base / relative
        if p.exists():
            return p
    except ValueError:
        pass

    return None


def resolve_with_cache(
    local_path: Path,
    project_path: Path,
) -> Tuple[Path, bool]:
    """Resolve a file path with external-disk fallback (read-only).

    Resolution order:
      1. local_path (always checked first)
      2. [cache] path  — typically GnommoDisk
      3. [assets] path — optional second drive (e.g. LaCie)

    Returns:
        Tuple of (resolved_path, is_from_external) where is_from_external=True
        when the file was found on an external drive rather than locally.
    """
    if local_path.exists():
        return local_path, False

    for base in (load_cache_config(), load_assets_config()):
        if base is None:
            continue
        resolved = _resolve_against_base(local_path, project_path, base)
        if resolved is not None:
            return resolved, True

    return local_path, False


def load_server_config() -> Optional[dict]:
    """Load server rsync config from ~/.gnommo.conf.

    Expected config:
        [server]
        host = 76.13.144.52
        user = root
        path = /gnommo/project

    Returns:
        Dict with keys host, user, path (and optionally port), or None.
    """
    config_path = Path.home() / ".gnommo.conf"
    if not config_path.exists():
        return None

    config = configparser.ConfigParser()
    config.read(config_path)

    if not config.has_section("server"):
        return None

    host = config.get("server", "host", fallback=None)
    user = config.get("server", "user", fallback="root")
    path = config.get("server", "path", fallback="/gnommo/project")
    port = config.get("server", "port", fallback="22")

    if not host:
        return None

    return {"host": host, "user": user, "path": path, "port": port}


def is_cache_configured() -> bool:
    """Check if any external fallback is configured."""
    return load_cache_config() is not None or load_assets_config() is not None


def get_cache_info() -> Optional[str]:
    """Get a human-readable string of all configured external paths."""
    parts = []
    for label, path in (("cache", load_cache_config()), ("assets", load_assets_config())):
        if path is None:
            continue
        status = "connected" if path.exists() else "not connected"
        parts.append(f"{path} ({status})")
    return "; ".join(parts) if parts else None
