import logging
from datetime import datetime
from typing import Optional, Union

import tzlocal

from pyhanko.pdf_utils import generic, misc

from . import model

__all__ = ['update_info_dict', 'view_from_info_dict']

logger = logging.getLogger(__name__)


def _write_meta_string(dictionary: generic.DictionaryObject,
                       key: str, meta_str: model.MetaString,
                       existing_only: bool) -> bool:

    if isinstance(meta_str, misc.StringWithLanguage):
        # don't bother with embedding language codes in strings,
        # too few people implement escapes anyway, so meh
        string = meta_str.value
    elif isinstance(meta_str, str):
        string = meta_str
    else:
        return False

    pdf_str = generic.TextStringObject(string)
    try:
        old_value = dictionary[key]
        mod = old_value != pdf_str
    except KeyError:
        mod = not existing_only
    if mod:
        dictionary[key] = pdf_str
    return mod


def _write_meta_date(dictionary: generic.DictionaryObject,
                     key: str, meta_date: Union[datetime, str, None],
                     existing_only: bool) -> bool:
    if isinstance(meta_date, datetime):
        value = meta_date
    elif meta_date == 'now':
        value = datetime.now(tz=tzlocal.get_localzone())
    else:
        return False

    if not existing_only or key in dictionary:
        dictionary[key] = generic.pdf_date(value)
        return True
    else:
        return False


def update_info_dict(meta: model.DocumentMetadata,
                     info: generic.DictionaryObject,
                     only_update_existing: bool = False) -> bool:

    mod = _write_meta_date(
        info, "/ModDate", meta.last_modified,
        existing_only=only_update_existing
    )
    producer = model.VENDOR
    try:
        producer_string = info['/Producer']
        if producer not in producer_string:
            producer_string = \
                generic.TextStringObject(f"{producer_string}; {producer}")
            mod = True
    except (KeyError, TypeError):
        producer_string = generic.TextStringObject(producer)
        mod = True
    # always override this
    info['/Producer'] = producer_string

    if meta.xmp_unmanaged:
        return mod

    mod |= _write_meta_string(
        info, "/Title", meta.title,
        existing_only=only_update_existing
    )
    mod |= _write_meta_string(
        info, "/Author", meta.author,
        existing_only=only_update_existing
    )
    mod |= _write_meta_string(
        info, "/Subject", meta.subject,
        existing_only=only_update_existing
    )
    mod |= _write_meta_string(
        info, "/Creator", meta.creator,
        existing_only=only_update_existing
    )
    mod |= _write_meta_date(
        info, "/CreationDate", meta.created,
        existing_only=only_update_existing
    )

    if meta.keywords:
        info['/Keywords'] = generic.TextStringObject(','.join(meta.keywords))
        mod = True

    return mod


def _read_date_from_dict(info_dict: generic.DictionaryObject,
                         key: str) -> Optional[datetime]:
    try:
        date_str = info_dict[key]
    except KeyError:
        return None

    try:
        if isinstance(date_str,
                      (generic.TextStringObject, generic.ByteStringObject)):
            return generic.parse_pdf_date(date_str)
    except misc.PdfReadError:
        pass

    logger.warning(
        "Key %s in info dict has value %s, which is not a valid date string",
        key, repr(date_str)
    )
    return None


def view_from_info_dict(info_dict: generic.DictionaryObject) \
        -> model.DocumentMetadata:
    kwargs = {}
    for s_entry in ('title', 'author', 'subject', 'creator'):
        try:
            kwargs[s_entry] = str(info_dict[f"/{s_entry.title()}"])
        except KeyError:
            pass

    creation_date = _read_date_from_dict(info_dict, '/CreationDate')
    if creation_date is not None:
        kwargs['created'] = creation_date

    mod_date = _read_date_from_dict(info_dict, '/ModDate')
    if mod_date is not None:
        kwargs['last_modified'] = mod_date

    if '/Keywords' in info_dict:
        kwargs['keywords'] = str(info_dict['/Keywords']).split(',')

    return model.DocumentMetadata(**kwargs)
