#!/usr/bin/env python3
# coding: utf8

from pathlib import Path
from json import loads
from subprocess import run, CalledProcessError, PIPE
from . import Utils
from .BaseMedia import BaseMedia
from typing import Dict, List, Union, Any, Optional
from datetime import datetime
from mimetypes import guess_type
import exif
import re

JSONValue = Union[str, int, float, bool, None, Dict[str, Any], List[Any]]
JSONType = Union[Dict[str, JSONValue], List[JSONValue]]

# command to extract creation date from video files
FF_PROBE = [
    "ffprobe",
    "-v",
    "quiet",
    "-print_format",
    "json",
    "-show_entries",
    "stream=index,codec_type:stream_tags=creation_time:format_" "tags=creation_time",
]

# Huawei adds these camera modes to description but Google Photos seems wise to
# it and does not report this in its description metadata
# noinspection SpellCheckingInspection
HUAWEI_JUNK = [
    "jhdr",
    "edf",
    "sdr",
    "cof",
    "nor",
    "mde",
    "oznor",
    "btf",
    "btfmdn",
    "ptfbty",
    "mef",
    "bsh",
    "dav",
    "rpt",
    "fbt",
    "burst",
    "rhdr",
    "fbtmdn",
    "ptr",
    "rbtoz",
    "btr",
    "rbsh",
    "btroz",
]
# regex to check if this (might be) a duplicate with ' (n)' suffix. Note that
# 'demo (0).jpg' and 'demo (1).jpg' are not in the scheme
# but 'demo (2).jpg' to 'demo (999).jpg' are
DUPLICATE_MATCH = re.compile(r"(.*) \(([2-9]|\d{2,3})\)\.(.*)")


class LocalFilesMedia(BaseMedia):
    def __init__(self, full_path: Path):
        super(LocalFilesMedia, self).__init__()
        (mime, _) = guess_type(str(full_path))
        self.__mime_type: str = mime or "application/octet-stream"
        self.is_video: bool = self.__mime_type.startswith("video")
        self.__full_path: Path = full_path
        self.__original_name: str = full_path.name
        self.__ffprobe_installed = True
        self.__createDate: datetime = None

        self.got_meta: bool = False
        self.__exif_0: dict = {}
        self.__exif: dict = {}

        matches = DUPLICATE_MATCH.match(str(full_path.name))
        if matches:
            # this is (probably) a duplicate with 'file (n).jpg' format
            # extract the original name and duplicate no.
            # -1 is because the first duplicate is labelled ' (2)'
            self.duplicate_number: int = int(matches[2]) - 1
            self.__original_name = matches[1] + "." + matches[3]

        if self.is_video:
            self.get_video_meta()
        else:
            self.get_exif()
            self.get_image_date()

    def get_video_meta(self):
        if self.__ffprobe_installed:
            try:
                command = FF_PROBE + [str(self.__full_path)]
                result = run(command, stdout=PIPE, check=True)
                out = str(result.stdout.decode("utf-8"))
                json = loads(out)
                t = json["format"]["tags"]["creation_time"]
                self.__createDate = Utils.string_to_date(t)
                self.got_meta = True
            except FileNotFoundError:
                # this means there is no ffprobe installed
                self.__ffprobe_installed = False
            except CalledProcessError:
                pass
            except KeyError:
                # ffprobe worked but there is no creation time in the JSON
                pass

        if not self.__createDate:
            # just use file date
            self.__createDate = datetime.utcfromtimestamp(
                self.__full_path.stat().st_mtime
            )

    def get_image_date(self):
        p_date = None
        if self.got_meta:
            try:
                # noinspection PyUnresolvedReferences
                p_date = Utils.string_to_date(self.__exif.datetime_original)
            except (AttributeError, ValueError, KeyError):
                try:
                    # noinspection PyUnresolvedReferences
                    p_date = Utils.string_to_date(self.__exif.datetime)
                except (AttributeError, ValueError, KeyError):
                    pass
        if not p_date:
            # just use file date
            p_date = datetime.utcfromtimestamp(self.__full_path.stat().st_mtime)
        self.__createDate = p_date

    def get_exif(self):
        try:
            with open(str(self.relative_folder / self.filename), "rb") as image_file:
                self.__exif = exif.Image(image_file)
            self.got_meta = True
        except (IOError, AssertionError):
            self.got_meta = False

    @property
    def uid(self) -> str:
        if not self.got_meta:
            uid = "none"
        elif self.is_video:
            uid = "not_supported"
        else:
            try:
                # noinspection PyUnresolvedReferences
                uid = self.__exif.image_unique_id
            except (AttributeError, KeyError):
                uid = "no_uid_in_exif"
        return uid

    # ----- override Properties below -----
    @property
    def relative_folder(self) -> Path:
        return self.__full_path.parent

    @property
    def size(self) -> int:
        s = self.__full_path.stat().st_size
        return s

    @property
    def id(self) -> Optional[str]:
        return None

    @property
    def description(self) -> str:
        try:
            # noinspection PyUnresolvedReferences
            result = self.__exif.image_description
        except (AttributeError, KeyError, ValueError):
            result = None
        if result:
            if result in HUAWEI_JUNK:
                result = ""
        else:
            result = ""
        return result

    @property
    def orig_name(self) -> str:
        return self.__original_name

    @property
    def create_date(self) -> datetime:
        return self.__createDate

    @property
    def modify_date(self) -> datetime:
        return self.create_date

    @property
    def mime_type(self) -> str:
        return self.__mime_type

    @property
    def url(self) -> Optional[str]:
        return None

    @property
    def camera_model(self):
        try:
            # noinspection PyUnresolvedReferences
            cam = "{} {}".format(self.__exif.make, self.__exif.model)
        except (AttributeError, KeyError):
            cam = None
        return cam