# Copyright (C) 2014-2023 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

"""
Utilities aimed to help achieve mostly basic tasks.
"""




import re
import os
import time
import datetime
import logging

from rhodecode.lib.vcs.conf import settings
from rhodecode.lib.vcs.exceptions import VCSError, VCSBackendNotSupportedError


log = logging.getLogger(__name__)


def get_scm(path):
    """
    Returns one of alias from ``ALIASES`` (in order of precedence same as
    shortcuts given in ``ALIASES``) and working dir path for the given
    argument. If no scm-specific directory is found or more than one scm is
    found at that directory, ``VCSError`` is raised.
    """
    if not os.path.isdir(path):
        raise VCSError("Given path %s is not a directory" % path)

    found_scms = [(scm, path) for scm in get_scms_for_path(path)]

    if len(found_scms) > 1:
        found = ', '.join(x[0] for x in found_scms)
        raise VCSError(
            f'More than one [{found}] scm found at given path {path}')

    if len(found_scms) == 0:
        raise VCSError('No scm found at given path %s' % path)

    return found_scms[0]


def get_scm_backend(backend_type):
    from rhodecode.lib.vcs.backends import get_backend
    return get_backend(backend_type)


def get_scms_for_path(path):
    """
    Returns all scm's found at the given path. If no scm is recognized
    - empty list is returned.

    :param path: path to directory which should be checked. May be callable.

    :raises VCSError: if given ``path`` is not a directory
    """
    from rhodecode.lib.vcs.backends import get_backend
    if hasattr(path, '__call__'):
        path = path()
    if not os.path.isdir(path):
        raise VCSError("Given path %r is not a directory" % path)

    result = []
    for key in settings.available_aliases():
        try:
            backend = get_backend(key)
        except VCSBackendNotSupportedError:
            log.warning('VCSBackendNotSupportedError: %s not supported', key)
            continue
        if backend.is_valid_repository(path):
            result.append(key)
    return result


def parse_datetime(text):
    """
    Parses given text and returns ``datetime.datetime`` instance or raises
    ``ValueError``.

    :param text: string of desired date/datetime or something more verbose,
      like *yesterday*, *2weeks 3days*, etc.
    """
    if not text:
        raise ValueError('Wrong date: "%s"' % text)

    if isinstance(text, datetime.datetime):
        return text

    # we limit a format to no include microseconds e.g 2017-10-17t17:48:23.XXXX
    text = text.strip().lower()[:19]

    input_formats = (
        '%Y-%m-%d %H:%M:%S',
        '%Y-%m-%dt%H:%M:%S',
        '%Y-%m-%d %H:%M',
        '%Y-%m-%dt%H:%M',
        '%Y-%m-%d',
        '%m/%d/%Y %H:%M:%S',
        '%m/%d/%Yt%H:%M:%S',
        '%m/%d/%Y %H:%M',
        '%m/%d/%Yt%H:%M',
        '%m/%d/%Y',
        '%m/%d/%y %H:%M:%S',
        '%m/%d/%yt%H:%M:%S',
        '%m/%d/%y %H:%M',
        '%m/%d/%yt%H:%M',
        '%m/%d/%y',
    )
    for format_def in input_formats:
        try:
            return datetime.datetime(*time.strptime(text, format_def)[:6])
        except ValueError:
            pass

    # Try descriptive texts
    if text == 'tomorrow':
        future = datetime.datetime.now() + datetime.timedelta(days=1)
        args = future.timetuple()[:3] + (23, 59, 59)
        return datetime.datetime(*args)
    elif text == 'today':
        return datetime.datetime(*datetime.datetime.today().timetuple()[:3])
    elif text == 'now':
        return datetime.datetime.now()
    elif text == 'yesterday':
        past = datetime.datetime.now() - datetime.timedelta(days=1)
        return datetime.datetime(*past.timetuple()[:3])
    else:
        days = 0
        matched = re.match(
            r'^((?P<weeks>\d+) ?w(eeks?)?)? ?((?P<days>\d+) ?d(ays?)?)?$', text)
        if matched:
            groupdict = matched.groupdict()
            if groupdict['days']:
                days += int(matched.groupdict()['days'])
            if groupdict['weeks']:
                days += int(matched.groupdict()['weeks']) * 7
            past = datetime.datetime.now() - datetime.timedelta(days=days)
            return datetime.datetime(*past.timetuple()[:3])

    raise ValueError('Wrong date: "%s"' % text)