import json
import logging
import os
from datetime import datetime

import pytz
from PIL import Image, IptcImagePlugin, TiffImagePlugin

logger = logging.getLogger(__name__)

""" Required exif values """
TAGS_EXIF = {
    0x9003: "DateTimeOriginal",
    0x829D: "FNumber",
    0x829A: "ExposureTime",
    0x8827: "ISOSpeedRatings",
    0x9204: "ExposureBiasValue",
    0x8822: "ExposureProgram",
    0x9207: "MeteringMode",
    0x920A: "FocalLength",
    0xA405: "FocalLengthIn35mmFilm",
    0x0110: "Model",
    0xA434: "LensModel",
}

""" Required iptc values """
TAGS_IPTC = {
    (2, 25): "Keywords",
    (2, 55): "DateCreated",
    (2, 60): "TimeCreated",
}


class Exif:

    def __init__(self, filename: str):
        self.filename = filename
        self._exif = {}
        self._iptc = {}
        self.data = {}

        self._process_image()
        self._read_exif()
        self._read_iptc()

    def _process_image(self):
        """ Open and verify image file """
        try:
            with Image.open(self.filename) as self.img:
                self.img.verify()
        except (OSError, IOError) as e:
            logger.exception('Could not open or verify image: %s', self.filename)
            raise e

    def _read_exif(self):
        """ Read and process EXIF metadata """
        try:
            exif_data = self.img._getexif()
            if exif_data is not None:
                self._exif = {
                    TAGS_EXIF[k]: v
                    for k, v in exif_data.items()
                    if k in TAGS_EXIF
                }
                for key, value in self._exif.items():
                    if isinstance(value, TiffImagePlugin.IFDRational):
                        self.data[key] = float(value)
                    elif isinstance(value, tuple):
                        self.data[key] = tuple(float(t) if isinstance(t, TiffImagePlugin.IFDRational) else t for t in value)
                    elif isinstance(value, bytes):
                        self.data[key] = value.decode('utf-8')
                    else:
                        self.data[key] = value
        except AttributeError:
            logger.warning('No EXIF metadata found for: %s', self.filename)
        except Exception as e:
            logger.warning('Could not read EXIF metadata from: %s, Error: %s', self.filename, e)

    def _read_iptc(self):
        """ Read and process IPTC metadata """
        try:
            iptc_data = IptcImagePlugin.getiptcinfo(self.img)
            if iptc_data is not None:
                self._iptc = {
                    TAGS_IPTC[k]: v
                    for k, v in iptc_data.items()
                    if k in TAGS_IPTC
                }
                for key, value in self._iptc.items():
                    if isinstance(value, list):
                        self.data[key] = [x.decode('utf-8') for x in value]
                    else:
                        self.data[key] = value.decode('utf-8')
        except Exception as e:
            logger.warning('Could not read IPTC metadata from: %s, Error: %s', self.filename, e)

    def datetimeoriginal(self, timezone='UTC'):
        """ Return DateTimeOriginal with timezone, or fallback to file creation date if not available. """
        if 'DateTimeOriginal' in self.data:
            try:
                return datetime.strptime(self.data['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S").astimezone(pytz.timezone(timezone))
            except ValueError:
                logger.warning('Invalid EXIF DateTimeOriginal format in: %s', self.filename)

        # If EXIF DateTimeOriginal is not available, fallback to file creation date
        try:
            creation_time = os.path.getctime(self.filename)
            return datetime.fromtimestamp(creation_time).astimezone(pytz.timezone(timezone))
        except Exception as e:
            logger.warning('Could not retrieve file creation date for: %s, Error: %s', self.filename, e)
            return datetime.now(pytz.timezone(timezone))  # Fallback to current time if all else fails

    def json(self):
        """ Return exif data in json format """
        return json.dumps(self.data)

    def keywords(self):
        """ Return Keywords list """
        return self.data.get('Keywords', [])

    def __str__(self):
        return self.filename