"""EXIF metadata extraction for uploaded files."""
from __future__ import annotations

import io


def extract_metadata(content: bytes, mime_type: str) -> dict:
    result: dict = {}

    if mime_type.startswith("image/"):
        result.update(_extract_image_metadata(content))

    return result


def _extract_image_metadata(content: bytes) -> dict:
    try:
        from PIL import Image, ExifTags
        img = Image.open(io.BytesIO(content))
        result: dict = {"width": img.width, "height": img.height}

        raw = img._getexif()  # type: ignore[attr-defined]
        if not raw:
            return result

        tags = {ExifTags.TAGS.get(k, k): v for k, v in raw.items()}

        _copy_str(tags, "Make", result, "camera_make")
        _copy_str(tags, "Model", result, "camera_model")
        _copy_str(tags, "LensModel", result, "lens")

        focal = tags.get("FocalLength")
        if focal is not None:
            result["focal_length"] = _to_float(focal)

        aperture = tags.get("FNumber")
        if aperture is not None:
            result["aperture"] = _to_float(aperture)

        shutter = tags.get("ExposureTime")
        if shutter is not None:
            result["shutter_speed"] = _to_float(shutter)

        iso = tags.get("ISOSpeedRatings")
        if iso is not None:
            try:
                result["iso"] = int(iso)
            except (TypeError, ValueError):
                pass

        flash = tags.get("Flash")
        if flash is not None:
            try:
                result["flash"] = int(flash)
            except (TypeError, ValueError):
                pass

        orientation = tags.get("Orientation")
        if orientation is not None:
            try:
                result["orientation"] = int(orientation)
            except (TypeError, ValueError):
                pass

        date_str = tags.get("DateTimeOriginal") or tags.get("DateTime")
        if date_str:
            ts = _parse_exif_datetime(str(date_str))
            if ts is not None:
                result["date_taken"] = ts

        gps_info = tags.get("GPSInfo")
        if gps_info and isinstance(gps_info, dict):
            gps_tags = {ExifTags.GPSTAGS.get(k, k): v for k, v in gps_info.items()}
            lat = _dms_to_decimal(gps_tags.get("GPSLatitude"), gps_tags.get("GPSLatitudeRef"))
            lon = _dms_to_decimal(gps_tags.get("GPSLongitude"), gps_tags.get("GPSLongitudeRef"))
            if lat is not None:
                result["gps_lat"] = lat
            if lon is not None:
                result["gps_lon"] = lon

        return result
    except Exception:
        return {}


def _copy_str(tags: dict, tag_key: str, result: dict, out_key: str) -> None:
    val = tags.get(tag_key)
    if val:
        s = str(val).strip()
        if s:
            result[out_key] = s


def _to_float(val) -> float | None:
    try:
        if hasattr(val, "numerator") and hasattr(val, "denominator"):
            if val.denominator == 0:
                return None
            return float(val.numerator) / float(val.denominator)
        return float(val)
    except (TypeError, ValueError, ZeroDivisionError):
        return None


def _parse_exif_datetime(s: str) -> float | None:
    import time as _time
    for fmt in ("%Y:%m:%d %H:%M:%S", "%Y-%m-%d %H:%M:%S"):
        try:
            return _time.mktime(_time.strptime(s, fmt))
        except (ValueError, OverflowError):
            pass
    return None


def _dms_to_decimal(dms, ref) -> float | None:
    if not dms or not ref:
        return None
    try:
        d = _to_float(dms[0])
        m = _to_float(dms[1])
        s = _to_float(dms[2])
        if d is None or m is None or s is None:
            return None
        decimal = d + m / 60.0 + s / 3600.0
        if ref in ("S", "W"):
            decimal = -decimal
        return round(decimal, 7)
    except (IndexError, TypeError):
        return None
