"""Static image path resolution, case-insensitive lookup and the `img` Jinja global."""
import io
import os
import re
from pathlib import Path

from nexora.config import IMAGE_ROOT, STATIC_DIR, UPLOAD_DIR, logger
from nexora.core import app
from nexora.database.connection import _ttl_cache, _ttl_cache_lock


# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
# Centralized image upload processing
# Every image upload on the site goes through process_image_upload():
#   1. extension allowlist (untrusted filename never trusted for storage)
#   2. real byte-size + content validation via Pillow (rejects executables,
#      corrupt payloads, decompression bombs)
#   3. automatic WebP conversion with progressive quality/scale reduction
#   4. hard server-side rule: final stored file must be â‰¤ MAX_STORED_IMAGE_BYTES
#   5. safe, server-generated filename; original upload is never kept
# Returns (filename, error): (None, "") = no file provided (not an error),
# ("x.webp", "") = success, (None, "message") = rejected.
# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
MAX_STORED_IMAGE_BYTES = 20 * 1024        # final stored WebP must be â‰¤ 20 KB


_IMAGE_EXTENSIONS = (".webp", ".png", ".jpg", ".jpeg", ".gif", ".jfif", ".avif", ".bmp", ".tiff", ".tif", ".svg", ".heic", ".heif", ".pdf", ".ico")


_IMG_CACHE = {}
_ABS_CACHE = {}


_STATIC_CASE_INDEX = None
_MISSING_SENTINEL = object()


def _clean_static_reference(reference):
    value = (reference or "").strip().replace("\\", "/")
    while value.startswith("/"):
        value = value[1:]
    while value.lower().startswith("static/"):
        value = value[7:].lstrip("/")
    value = re.sub(r"/+", "/", value)
    if not value or "\x00" in value:
        return ""
    parts = [part for part in value.split("/") if part not in ("", ".")]
    if any(part == ".." for part in parts):
        return ""
    value = "/".join(parts)
    if value and not (value.lower().startswith("images/") or value.lower().startswith("uploads/")):
        value = "uploads/" + value
    return value


def _inside_static(path):
    try:
        rel = os.path.relpath(str(path.resolve()), str(STATIC_DIR.resolve()))
        return rel == "." or not rel.startswith("..")
    except (ValueError, OSError):
        return False


def _static_relative(path):
    return os.path.relpath(str(path.resolve()), str(STATIC_DIR.resolve())).replace("\\", "/")


def _static_case_index():
    global _STATIC_CASE_INDEX
    if _STATIC_CASE_INDEX is not None:
        return _STATIC_CASE_INDEX
    index = {}
    _IMG_EXTS = {".webp", ".png", ".jpg", ".jpeg", ".gif", ".jfif", ".avif", ".bmp", ".tiff", ".tif", ".svg", ".heic", ".heif", ".pdf", ".ico"}
    for root in (IMAGE_ROOT, UPLOAD_DIR):
        if not root.is_dir():
            continue
        try:
            for found in root.rglob("*"):
                if found.is_file() and found.suffix.lower() in _IMG_EXTS:
                    rel = found.relative_to(STATIC_DIR).as_posix()
                    index.setdefault(rel.lower(), rel)
                    index.setdefault(f"basename:{found.name.lower()}", rel)
        except OSError:
            continue
    _STATIC_CASE_INDEX = index
    return _STATIC_CASE_INDEX


def invalidate_image_cache():
    global _STATIC_CASE_INDEX
    _IMG_CACHE.clear()
    _ABS_CACHE.clear()
    _STATIC_CASE_INDEX = None
    with _ttl_cache_lock:
        _ttl_cache.clear()


def _resolve_ext_fallback(base_dir, value):
    """Try *value* as-is first, then swap its extension through every
    supported image type.  Returns the first existing file, or the original
    path if nothing else exists (preserving previous behaviour)."""
    value = _clean_static_reference(value)
    if not value:
        return None
    p = (base_dir / value).resolve()
    if _inside_static(p) and p.is_file():
        return p
    indexed = _static_case_index().get(value.lower())
    if indexed:
        p = (base_dir / indexed).resolve()
        if _inside_static(p) and p.is_file():
            return p
    stem = p.stem
    parent_rel = Path(value).parent.as_posix()
    for ext in _IMAGE_EXTENSIONS:
        rel = f"{parent_rel}/{stem}{ext}" if parent_rel != "." else f"{stem}{ext}"
        indexed = _static_case_index().get(rel.lower())
        candidate = (base_dir / (indexed or rel)).resolve()
        if _inside_static(candidate) and candidate.is_file():
            return candidate
    return p


def static_file_abs(reference):
    """Resolve a stored image reference to an absolute path inside static/.
    Accepts legacy slashes/prefixes and resolves Linux filename case safely."""
    value = _clean_static_reference(reference)
    if not value:
        return None
    cached = _ABS_CACHE.get(value, _MISSING_SENTINEL)
    if cached is not _MISSING_SENTINEL:
        return (STATIC_DIR / cached).resolve() if cached else None
    try:
        target = _resolve_ext_fallback(STATIC_DIR, value)
        if target and target.is_file():
            _ABS_CACHE[value] = _static_relative(target)
            return target
        basename = os.path.splitext(os.path.basename(value))[0]
        for ext in _IMAGE_EXTENSIONS:
            rel = f"uploads/{basename}{ext}"
            indexed = _static_case_index().get(rel.lower())
            candidate = (STATIC_DIR / (indexed or rel)).resolve()
            if _inside_static(candidate) and candidate.is_file():
                _ABS_CACHE[value] = _static_relative(candidate)
                return candidate
        original_name = os.path.basename(value).lower()
        indexed = _static_case_index().get(f"basename:{original_name}")
        if indexed:
            candidate = (STATIC_DIR / indexed).resolve()
            if _inside_static(candidate) and candidate.is_file():
                _ABS_CACHE[value] = _static_relative(candidate)
                return candidate
        for ext in _IMAGE_EXTENSIONS:
            indexed = _static_case_index().get(f"basename:{basename.lower()}{ext}")
            if indexed:
                candidate = (STATIC_DIR / indexed).resolve()
                if _inside_static(candidate) and candidate.is_file():
                    _ABS_CACHE[value] = _static_relative(candidate)
                    return candidate
    except (ValueError, OSError):
        pass
    _ABS_CACHE[value] = ""
    return None


def img_static_url(value):
    """Jinja helper: map any stored image reference to its static/-relative
    URL path without exposing protected registration proofs.
    Returns '' when the file cannot be found on disk so the template
    never produces a broken <img> that 404s."""
    v = _clean_static_reference(value)
    if not v:
        return ""
    if v in _IMG_CACHE:
        return _IMG_CACHE[v]
    target = static_file_abs(v)
    if target and target.is_file():
        result = _static_relative(target)
        _IMG_CACHE[v] = result
        return result
    # ── Fallback 1: case-insensitive index (already built, O(1)) ──
    indexed = _static_case_index().get(v.lower())
    if indexed:
        candidate = (STATIC_DIR / indexed).resolve()
        if _inside_static(candidate) and candidate.is_file():
            result = _static_relative(candidate)
            _IMG_CACHE[v] = result
            return result
    # File not found: cache empty result so repeated lookups skip filesystem.
    _IMG_CACHE[v] = ""
    return ""


app.jinja_env.globals["img"] = img_static_url
