import os
import time
from datetime import datetime, timezone

from ..constants import ISO_FORMAT, ISO_FORMAT_NO_USECS


def to_localtime(ts):
    """Convert a datetime object from UTC to the local time zone."""
    return datetime(*time.localtime((ts - datetime(1970, 1, 1, tzinfo=timezone.utc)).total_seconds())[:6])


def utcnow():
    """Return a naive datetime instance representing the time in the UTC time zone."""
    return datetime.now(timezone.utc).replace(tzinfo=None)


def parse_timestamp(timestamp, tzinfo=timezone.utc):
    """Parse an ISO 8601 timestamp string."""
    fmt = ISO_FORMAT if '.' in timestamp else ISO_FORMAT_NO_USECS
    dt = datetime.strptime(timestamp, fmt)
    if tzinfo is not None:
        dt = dt.replace(tzinfo=tzinfo)
    return dt


def timestamp(s):
    """Convert a --timestamp=s argument to a datetime object."""
    try:
        # Is it pointing to a file/directory?
        ts = safe_s(os.stat(s).st_mtime)
        return datetime.fromtimestamp(ts, tz=timezone.utc)
    except OSError:
        # Didn't work; try parsing as a timestamp. UTC, no time zone, no microseconds support.
        for format in ('%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S+00:00',
                       '%Y-%m-%dT%H:%M:%S', '%Y-%m-%d %H:%M:%S',
                       '%Y-%m-%dT%H:%M', '%Y-%m-%d %H:%M',
                       '%Y-%m-%d', '%Y-%j',
                       ):
            try:
                return datetime.strptime(s, format).replace(tzinfo=timezone.utc)
            except ValueError:
                continue
        raise ValueError


# Not too rarely, we get crappy timestamps from the fs, that overflow some computations.
# As they are crap anyway (valid filesystem timestamps always refer to the past up to
# the present, but never to the future), nothing is lost if we just clamp them to the
# maximum value we can support.
# As long as people are using Borg on 32-bit platforms to access Borg archives, we must
# keep this value True. But we can expect that we can stop supporting 32-bit platforms
# well before coming close to the year 2038, so this will never be a practical problem.
SUPPORT_32BIT_PLATFORMS = True  # set this to False before y2038.

if SUPPORT_32BIT_PLATFORMS:
    # second timestamps will fit into a signed int32 (platform time_t limit).
    # nanosecond timestamps thus will naturally fit into a signed int64.
    # Subtract the last 48 h to avoid any issues that could be caused by time zone calculations.
    # This is in the year 2038, so it is also less than y9999 (which is a datetime internal limit).
    # msgpack can pack up to uint64.
    MAX_S = 2**31-1 - 48*3600
    MAX_NS = MAX_S * 1000000000
else:
    # nanosecond timestamps will fit into a signed int64.
    # Subtract the last 48 h to avoid any issues that could be caused by time zone calculations.
    # This is in the year 2262, so it is also less than y9999 (which is a datetime internal limit).
    # Round down to a 1e9 multiple so MAX_NS corresponds precisely to an integer MAX_S.
    # msgpack can pack up to uint64.
    MAX_NS = (2**63-1 - 48*3600*1000000000) // 1000000000 * 1000000000
    MAX_S = MAX_NS // 1000000000


def safe_s(ts):
    if 0 <= ts <= MAX_S:
        return ts
    elif ts < 0:
        return 0
    else:
        return MAX_S


def safe_ns(ts):
    if 0 <= ts <= MAX_NS:
        return ts
    elif ts < 0:
        return 0
    else:
        return MAX_NS


def safe_timestamp(item_timestamp_ns):
    t_ns = safe_ns(item_timestamp_ns)
    return datetime.fromtimestamp(t_ns / 1e9)


def format_time(ts: datetime, format_spec=''):
    """Convert *ts* to a human-friendly format with textual weekday."""
    return ts.strftime('%a, %Y-%m-%d %H:%M:%S' if format_spec == '' else format_spec)


def isoformat_time(ts: datetime):
    """
    Format *ts* according to ISO 8601.
    """
    # note: first make all datetime objects tz aware before adding %z here.
    return ts.strftime(ISO_FORMAT)


def format_timedelta(td):
    """Format timedelta in a human friendly format
    """
    ts = td.total_seconds()
    s = ts % 60
    m = int(ts / 60) % 60
    h = int(ts / 3600) % 24
    txt = '%.2f seconds' % s
    if m:
        txt = '%d minutes %s' % (m, txt)
    if h:
        txt = '%d hours %s' % (h, txt)
    if td.days:
        txt = '%d days %s' % (td.days, txt)
    return txt


class OutputTimestamp:
    def __init__(self, ts: datetime):
        if ts.tzinfo == timezone.utc:
            ts = to_localtime(ts)
        self.ts = ts

    def __format__(self, format_spec):
        return format_time(self.ts, format_spec=format_spec)

    def __str__(self):
        return f'{self}'

    def isoformat(self):
        return isoformat_time(self.ts)

    to_json = isoformat
