import logging
import re
from typing import List, Callable

from django.conf import settings
from django.core.validators import RegexValidator
from django.utils.functional import SimpleLazyObject
from ids.exceptions import BackendError, EmptyIteratorError
from startrek_client.objects import Resource

from staff.lib.requests import get_ids_repository
from staff.lib.utils.retry import retry

logger = logging.getLogger(__name__)


class StartrekError(Exception):
    code = 'startrek_error'
    message = 'startrek_error'


startrek_issue_key_validator = RegexValidator(
    regex=r'^[A-Z]+-\d+$',
    code='incorrect_key_format',
    message='Некорректный формат тикета',
)


startrek_issues_repository = SimpleLazyObject(
    lambda: get_ids_repository(
        service='startrek2',
        resource_type='issues',
        oauth_token=settings.ROBOT_STAFF_OAUTH_TOKEN,
        user_agent=settings.STAFF_USER_AGENT,
        retries=3,
    )
)


def create_issue(queue: str, summary: str, description: str, **fields) -> Resource:
    """
    Создаёт тикет в Startrek.
    """
    params = dict(fields)
    params.update({
        'queue': queue,
        'summary': summary,
        'description': description,
    })

    try:
        issue = startrek_issues_repository.create(**params)
    except BackendError:
        logger.warning('Error during create issue in queue %s', queue, exc_info=True)
        raise StartrekError

    _fetch_issue_fields(issue)
    return issue


@retry(delay=0.1, backoff=1.5, max_tries=10, exceptions=EmptyIteratorError, logger=logger)
def get_issue(key: str = None, unique: str = None) -> Resource:
    """
    Получает тикет из Startrek по ключу или уникальному идентификатору.

    При запросе несуществующего тикета или тикета, к которому нет доступа, вызывается EmptyIteratorError.
    """
    assert key or unique
    if key:
        lookup = {'query': f'key:{key} or aliases:{key}'}
    else:
        lookup = {'filter': f'unique:{unique}'}
    return startrek_issues_repository.get_one(lookup=lookup)


def get_issues(keys: List[str]) -> List[Resource]:
    """
    Получает список тикетов из Startrek по ключам.

    При запросе несуществующих тикетов или тикетов, к которым нет доступа, они пропускаются.
    """
    return startrek_issues_repository.get(lookup={'filter': f'key:{",".join(keys)}'})


def get_issues_by_unique(unique: List[str]) -> List[Resource]:
    """
    Получает список тикетов из Startrek по ключам.

    При запросе несуществующих тикетов или тикетов, к которым нет доступа, они пропускаются.
    """
    return startrek_issues_repository.get(lookup={'filter': f'unique:{",".join(unique)}'})


def update_issue(key: str, **fields) -> None:
    """
    Редактирует тикет Startrek.
    """
    def callback(issue: Resource) -> None:
        issue.update(**fields)

    _process_issue(
        key=key,
        callback=callback,
        callback_description='updating',
    )


def add_comment(key: str, text: str, **fields) -> None:
    """
    Отправляет комментарий в тикет Startrek.
    """

    @retry(delay=0.1, backoff=1.5, max_tries=10)
    def callback(issue: Resource) -> None:
        issue.comments.create(text=text, **fields)

    _process_issue(
        key=key,
        callback=callback,
        callback_description='adding comment',
    )


def change_state(key: str, action_id: str) -> None:
    """
    Запускает transition у тикета
    """
    @retry(delay=0.1, backoff=1.5, max_tries=10)
    def callback(issue: Resource) -> None:
        issue.transitions[action_id].execute()

    _process_issue(key=key, callback=callback, callback_description='execute transition')


def _fetch_issue_fields(issue: Resource) -> None:
    """
    Загружает в тикет информацию о типах его полей.

    Note: Библиотека ids отдает тикет без мета-информации о типах его полей.
    При первом обращении к какому-либо полю производится запрос о типах всех полей.
    Данная функция позволяет предзагрузить их и сгруппировать все запросы в трекер в одном месте.
    """
    try:
        issue.key
    except BackendError:
        logger.warning('Error fetching issue fields from StarTrek API')


def _process_issue(key: str, callback: Callable, callback_description: str = '') -> None:
    issue = get_issue(key=key)
    try:
        callback(issue)
    except BackendError as exc:
        logger.warning(
            'Error processing the issue %s (%s)\n%s',
            key,
            callback_description,
            exc.extra,
            exc_info=True,
        )
        raise


_issue_queue_regexp = re.compile(r'^([A-Z]+)-\d+$')


def get_issue_queue(key: str) -> str:
    m = _issue_queue_regexp.match(key)
    return m and m.group(1) or None
