from __future__ import print_function
import datetime
import hashlib
import json
import os
import re
import tempfile

UTC = datetime.timezone.utc if hasattr(datetime, "timezone") else None


def utcnow():
    if UTC is not None:
        return datetime.datetime.now(UTC)
    return datetime.datetime.utcnow()


def isoformat(value=None):
    value = value or utcnow()
    text = value.isoformat()
    if text.endswith("+00:00"):
        text = text[:-6] + "Z"
    return text


def parse_time(value):
    if not value:
        return None
    text = str(value).strip()
    if text.endswith("Z"):
        text = text[:-1] + "+00:00"
    # Python 3.7+ path.
    if hasattr(datetime.datetime, "fromisoformat"):
        try:
            dt = datetime.datetime.fromisoformat(text)
            if dt.tzinfo is None and UTC is not None:
                dt = dt.replace(tzinfo=UTC)
            return dt
        except Exception:
            pass
    # Compatible with Python 3.5/3.6 and common NWS timestamp forms.
    match = re.match(r"^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?([+-]\d{2}:?\d{2})?$", text)
    if not match:
        return None
    year, month, day, hour, minute, second = [int(x) for x in match.groups()[:6]]
    micros = int(((match.group(7) or "0") + "000000")[:6])
    dt = datetime.datetime(year, month, day, hour, minute, second, micros)
    offset = match.group(8)
    if offset:
        sign = 1 if offset[0] == "+" else -1
        clean = offset[1:].replace(":", "")
        delta = datetime.timedelta(hours=int(clean[:2]), minutes=int(clean[2:]))
        dt = dt - sign * delta
    if UTC is not None:
        dt = dt.replace(tzinfo=UTC)
    return dt


def timestamp(value):
    if value is None:
        return None
    epoch = datetime.datetime(1970, 1, 1)
    if UTC is not None:
        epoch = epoch.replace(tzinfo=UTC)
        if value.tzinfo is None:
            value = value.replace(tzinfo=UTC)
    return (value - epoch).total_seconds()


def atomic_write(path, data, binary=False, mode=None):
    directory = os.path.dirname(path) or "."
    if not os.path.isdir(directory):
        os.makedirs(directory)
    fd, temporary = tempfile.mkstemp(prefix=".brs-", dir=directory)
    try:
        if binary:
            with os.fdopen(fd, "wb") as handle:
                handle.write(data)
        else:
            with os.fdopen(fd, "w") as handle:
                handle.write(data)
        if mode is not None:
            os.chmod(temporary, mode)
        os.replace(temporary, path) if hasattr(os, "replace") else os.rename(temporary, path)
    finally:
        if os.path.exists(temporary):
            os.unlink(temporary)


def atomic_copy(source, target):
    with open(source, "rb") as handle:
        atomic_write(target, handle.read(), binary=True)


def atomic_write_if_changed(path, data, binary=False, mode=None):
    try:
        existing_mode = "rb" if binary else "r"
        with open(path, existing_mode) as handle:
            if handle.read() == data:
                return False
    except IOError:
        pass
    atomic_write(path, data, binary=binary, mode=mode)
    return True


def atomic_copy_if_changed(source, target):
    with open(source, "rb") as handle:
        data = handle.read()
    return atomic_write_if_changed(target, data, binary=True)


def stable_hash(value):
    payload = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
    return hashlib.sha256(payload.encode("utf-8")).hexdigest()


def safe_text(value, max_length=1200):
    value = re.sub(r"\s+", " ", str(value or "")).strip()
    value = value.replace("<", " ").replace(">", " ")
    return value[:max_length]


def event_matches(event, patterns):
    import fnmatch
    event = str(event or "")
    for pattern in patterns or []:
        if fnmatch.fnmatchcase(event.lower(), str(pattern).lower()):
            return True
    return False
