"""Matplotlib utility functions: Chinese font setup, plot export, and common helpers."""

import logging
import os
import sys
from pathlib import Path
from typing import Any

from matplotlib import font_manager
from matplotlib.axes import Axes
from matplotlib.figure import Figure
import matplotlib.pyplot as plt

logger = logging.getLogger(__name__)


def _get_platform_font_paths() -> tuple[Path, ...]:
    """Return font search paths for the current platform (Windows, macOS, Linux)."""
    home = Path.home()
    if sys.platform == "win32" or sys.platform == "cygwin":
        windir = Path(os.environ.get("WINDIR", "C:\\Windows"))
        return (
            windir / "Fonts",
            home / "AppData" / "Local" / "Microsoft" / "Windows" / "Fonts",
            home / "fonts",
        )
    if sys.platform == "darwin":
        return (
            home / ".local" / "share" / "fonts",
            home / "fonts",
            Path("/System/Library/Fonts/Supplemental"),
            Path("/System/Library/Fonts"),
            Path("/Library/Fonts"),
        )
    return (
        home / ".local" / "share" / "fonts",
        home / "fonts",
        Path("/usr/share/fonts"),
        Path("/usr/local/share/fonts"),
    )


# Common paths to search for Chinese fonts (OS-specific)
# Extend with your project's special font path via setup_chinese_font(font_path=...)
DEFAULT_CHINESE_FONT_SEARCH_PATHS: tuple[Path, ...] = _get_platform_font_paths()

# Common Chinese font filenames to look for (Windows, macOS, Linux)
CHINESE_FONT_NAMES: tuple[str, ...] = (
    "SimHei.ttf",
    "SimSun.ttf",
    "SimSun.ttc",
    "simsun.ttc",  # Windows lowercase
    "msyh.ttc",  # Microsoft YaHei (Windows)
    "msyhbd.ttc",  # Microsoft YaHei Bold (Windows)
    "Microsoft YaHei.ttf",
    "NotoSansCJK-Regular.ttc",
    "NotoSansCJK-Regular.otf",
    "NotoSerifCJK-Regular.ttc",
    "SourceHanSansCN-Regular.otf",
    "SourceHanSerifCN-Regular.otf",
    "PingFang.ttc",
    "STHeiti Light.ttc",
    "STHeiti Medium.ttc",
    "FZSongS.ttf",
    "FZHei-B01S.ttf",
)


def _search_font_in_dir(directory: Path) -> Path | None:
    """Search for a Chinese font file in the given directory (non-recursive)."""
    if not directory.exists() or not directory.is_dir():
        return None
    for name in CHINESE_FONT_NAMES:
        candidate = directory / name
        if candidate.is_file():
            return candidate
    return None


def find_chinese_font(
    *,
    custom_path: str | Path | None = None,
    search_paths: tuple[Path, ...] | None = None,
) -> Path | None:
    """Find a Chinese font file from a custom path or default search locations.

    Args:
        custom_path: If provided, check this path first (file or directory).
        search_paths: Directories to search. If None, uses DEFAULT_CHINESE_FONT_SEARCH_PATHS.

    Returns:
        Path to font file if found, None otherwise.
    """
    if custom_path is not None:
        p = Path(custom_path)
        if p.is_file():
            return p
        if p.is_dir():
            found = _search_font_in_dir(p)
            if found is not None:
                return found

    paths = search_paths or DEFAULT_CHINESE_FONT_SEARCH_PATHS
    for directory in paths:
        found = _search_font_in_dir(directory)
        if found is not None:
            return found
    return None


def register_chinese_font(font_path: str | Path) -> str | None:
    """Register a Chinese font from a custom path for matplotlib use.

    Args:
        font_path: Path to the font file (e.g. .ttf, .otf).

    Returns:
        The font family name if registration succeeded, None otherwise.

    Raises:
        FileNotFoundError: If the font file does not exist.
    """
    path = Path(font_path)
    if not path.exists():
        raise FileNotFoundError(f"Font file not found: {path}")

    try:
        font_manager.fontManager.addfont(str(path))
        prop = font_manager.FontProperties(fname=str(path))
        family = prop.get_name()
        logger.debug("Registered Chinese font: %s from %s", family, path)
        return family
    except Exception as e:
        logger.exception("Failed to register font at %s: %s", path, e)
        return None


def apply_chinese_font(
    font_family: str | None,
    *,
    axes: Axes | None = None,
    font_size: int | float = 12,
) -> None:
    """Apply Chinese font to axes labels and tick labels.

    Args:
        font_family: Font family name (from register_chinese_font) or None to skip.
        axes: Target axes. If None, uses plt.gca().
        font_size: Base font size for labels.
    """
    if font_family is None:
        return
    ax = axes or plt.gca()
    fp = font_manager.FontProperties(family=font_family, size=font_size)
    fp_small = font_manager.FontProperties(family=font_family, size=font_size * 0.9)
    ax.set_title(ax.get_title(), fontproperties=fp)
    ax.set_xlabel(ax.get_xlabel(), fontproperties=fp)
    ax.set_ylabel(ax.get_ylabel(), fontproperties=fp)
    for label in ax.get_xticklabels() + ax.get_yticklabels():
        label.set_fontproperties(fp_small)


def setup_chinese_font(
    font_path: str | Path | None = None,
    *,
    custom_search_path: str | Path | None = None,
    search_paths: tuple[Path, ...] | None = None,
) -> str | None:
    """Find, register, and return Chinese font family from a custom or default path.

    Prefer font_path when given. Otherwise search custom_search_path or default paths.

    Args:
        font_path: Direct path to font file. Used first if provided.
        custom_search_path: Directory to search for Chinese fonts (before default paths).
        search_paths: Full list of directories to search (ignores DEFAULT_CHINESE_FONT_SEARCH_PATHS).

    Returns:
        Font family name if successful, None otherwise.
    """
    path: Path | None = None
    if font_path is not None:
        p = Path(font_path)
        if p.is_file():
            path = p
        elif p.is_dir():
            path = _search_font_in_dir(p)

    if path is None and custom_search_path is not None:
        path = find_chinese_font(custom_path=custom_search_path, search_paths=search_paths)

    if path is None:
        path = find_chinese_font(search_paths=search_paths)

    if path is None:
        logger.warning("No Chinese font found in custom or default paths")
        return None

    return register_chinese_font(path)


def save_plot(
    fig: Figure,
    path: str | Path,
    *,
    dpi: int = 150,
    bbox_inches: str = "tight",
    facecolor: str | None = None,
    edgecolor: str | None = None,
    fmt: str | None = None,
    **kwargs: Any,
) -> Path:
    """Save a matplotlib figure to file.

    Args:
        fig: Figure to save.
        path: Output path (extension determines format if fmt is None).
        dpi: Resolution for raster formats (png, jpg).
        bbox_inches: Bbox setting; "tight" crops empty margins.
        facecolor: Figure face color.
        edgecolor: Figure edge color.
        fmt: Override format (png, pdf, svg, jpg, etc.). If None, inferred from path.
        **kwargs: Passed to fig.savefig().

    Returns:
        Resolved Path to the saved file.

    Raises:
        ValueError: If path has no extension and fmt is None.
    """
    out = Path(path)
    file_fmt = fmt or (out.suffix.lstrip(".") or None)
    if file_fmt is None:
        raise ValueError("Cannot infer format: provide path with extension or fmt=")

    save_kwargs: dict[str, Any] = {
        "dpi": dpi,
        "bbox_inches": bbox_inches,
        **kwargs,
    }
    if facecolor is not None:
        save_kwargs["facecolor"] = facecolor
    if edgecolor is not None:
        save_kwargs["edgecolor"] = edgecolor

    fig.savefig(str(out), format=file_fmt, **save_kwargs)
    logger.debug("Saved plot to %s (format=%s)", out, file_fmt)
    return out.resolve()


def export_plot(
    fig: Figure,
    path: str | Path,
    *,
    dpi: int = 150,
    bbox_inches: str = "tight",
    **kwargs: Any,
) -> Path:
    """Alias for save_plot. Export figure to file."""
    return save_plot(fig, path, dpi=dpi, bbox_inches=bbox_inches, **kwargs)


def close_figure(fig: Figure) -> None:
    """Close a figure and release resources."""
    plt.close(fig)
