from __future__ import annotations

import json
import logging
import os
import shlex
import subprocess
from typing import TYPE_CHECKING, Any

import PIL
import PIL.Image

if TYPE_CHECKING:
    from staticsite.cache import Cache
    from staticsite.file import File

log = logging.getLogger("utils.images")


class ImageScanner:
    def __init__(self, cache: Cache):
        self.cache = cache

    def scan(self, src: File, mimetype: str) -> dict[str, Any]:
        key = f"{src.abspath}:{src.stat.st_mtime:.3f}"
        meta: dict[str, Any] | None = self.cache.get(key)
        if meta is None:
            meta = self.read_meta(src.abspath, mimetype)
            self.cache.put(key, meta)
        return meta

    def scan_file(self, pathname: str) -> dict[str, Any]:
        import mimetypes

        mimetypes.init()
        base, ext = os.path.splitext(pathname)
        mimetype = mimetypes.types_map.get(ext)
        if mimetype is None:
            return {}
        return self.read_meta(pathname, mimetype)

    def read_meta(self, pathname: str, mimetype: str) -> dict[str, Any]:
        # We can take our time here, since results are cached

        if mimetype == "image/svg+xml":
            return {}

        with PIL.Image.open(pathname) as img:
            meta = {
                "width": img.width,
                "height": img.height,
                "title": "",
            }

            if img.format not in ("XPM",):
                meta.update(self.read_meta_exiftool(pathname))

        return meta

    def read_meta_exiftool(self, pathname: str) -> dict[str, Any]:
        meta: dict[str, Any] = {}

        # It is important to use abspath here, as exiftool does not support the
        # usual -- convention to deal with files starting with a dash. With abspath
        # at least the file name will start with a /
        res = subprocess.run(
            ["exiftool", "-json", "-n", "-c", "%f", pathname], capture_output=True
        )
        if res.returncode != 0:
            log.warning(
                "%s: exiftool failed with code %d: %s",
                pathname,
                res.returncode,
                res.stderr.strip(),
            )
            return meta

        info = json.loads(res.stdout)[0]

        description = info.get("ImageDescription")
        if description is not None:
            meta["title"] = description

        artist = info.get("Artist")
        if artist is not None:
            meta["author"] = artist

        orientation = info.get("Orientation")
        if orientation is not None:
            # https://www.impulseadventure.com/photo/exif-orientation.html
            meta["image_orientation"] = int(orientation)

        copyright = info.get("CopyrightNotice")
        if copyright is not None:
            meta["copyright"] = copyright

        lat = info.get("GPSLatitude")
        if lat is not None:
            meta["lat"] = float(lat)

        lon = info.get("GPSLongitude")
        if lon is not None:
            meta["lon"] = float(lon)

        # "DateTime": "2017:05:09 21:27:42",
        # "GPSLatitudeRef": "North",
        # "GPSAltitude": "92 m",
        # "GPSTimeStamp": "19:27:43",
        # "GPSDateStamp": "2017:05:09",
        # "GPSDateTime": "2017:05:09 19:27:43Z",

        return meta

    def edit_meta_exiftool(
        self, pathname: str, changed: dict[str, Any], removed: list[str]
    ) -> bool:
        exif_args: list[str] = []

        if "title" in changed:
            exif_args.append(f"-ImageDescription={changed['title']}")

        if "author" in changed:
            exif_args.append(f"-Artist={changed['author']}")

        if "image_orientation" in changed:
            exif_args.append(f"-Orientation={changed['image_orientation']}")

        if "copyright" in changed:
            # See https://libre-software.net/edit-metadata-exiftool/
            exif_args.append(f"-rights={changed['copyright']}")
            exif_args.append(f"-CopyrightNotice={changed['copyright']}")

        # lat = info.get("GPSLatitude")
        # if lat is not None:
        #     print("EXIF LAT", lat)

        # lon = info.get("GPSLongitude")
        # if lon is not None:
        #     print("EXIF LON", lon)

        for name in removed:
            if name == "title":
                exif_args.append("-ImageDescription=")
            elif name == "author":
                exif_args.append("-Artist=")
            elif name == "image_orientation":
                exif_args.append("-Orientation=")
            elif name == "copyright":
                exif_args.append("-rights=")
                exif_args.append("-CopyrightNotice=")

        cmd = [
            "exiftool",
            "-c",
            "%f",
            "-overwrite_original",
            "-quiet",
            pathname,
        ] + exif_args
        res = subprocess.run(cmd)
        if res.returncode != 0:
            log.warning(
                "%s: %s failed with code %d: %s",
                pathname,
                " ".join(shlex.quote(x) for x in cmd),
                res.returncode,
            )
            return False

        return True
