import re
import inspect
import logging

# parse HTTP status code to get the code
HTTP_CODE = re.compile(r'^HTTP_(?P<code>\d{3})$')

logger = logging.getLogger(__name__)


def parse_comments(func):
    """
    parse function comments

    First line of comments will be saved as summary, and the rest
    will be saved as description.
    """
    doc = inspect.getdoc(func)
    if doc is None:
        return None, None
    doc = doc.split('\n', 1)
    if len(doc) == 1:
        return doc[0], None
    return doc[0], doc[1].strip()


def parse_request(func):
    """
    get json spec
    """
    data = {}
    if hasattr(func, 'json'):
        data = {
            'content': {
                'application/json': {
                    'schema': {
                        '$ref': f'#/components/schemas/{func.json}'
                    }
                }
            }
        }
    return data


def parse_params(func, params, models):
    """
    get spec for (query, headers, cookies)
    """
    if hasattr(func, 'query'):
        query = models[func.query]
        for name, schema in query['properties'].items():
            params.append({
                'name': name,
                'in': 'query',
                'schema': schema,
                'required': name in query.get('required', []),
            })

    if hasattr(func, 'headers'):
        headers = models[func.headers]
        for name, schema in headers['properties'].items():
            params.append({
                'name': name,
                'in': 'header',
                'schema': schema,
                'required': name in headers.get('required', []),
            })

    if hasattr(func, 'cookies'):
        cookies = models[func.cookies]
        for name, schema in cookies['properties'].items():
            params.append({
                'name': name,
                'in': 'cookie',
                'schema': schema,
                'required': name in cookies.get('required', []),
            })

    return params


def parse_resp(func):
    """
    get the response spec

    If this function does not have explicit ``resp`` but have other models,
    a ``422 Validation Error`` will be append to the response spec. Since
    this may be triggered in the validation step.
    """
    responses = {}
    if hasattr(func, 'resp'):
        responses = func.resp.generate_spec()

    if '422' not in responses and has_model(func):
        responses['422'] = {'description': 'Validation Error'}

    return responses


def has_model(func):
    """
    return True if this function have ``pydantic.BaseModel``
    """
    if any(hasattr(func, x) for x in ('query', 'json', 'headers')):
        return True

    if hasattr(func, 'resp') and func.resp.has_model():
        return True

    return False


def parse_code(http_code):
    """
    get the code of this HTTP status

    :param str http_code: format like ``HTTP_200``
    """
    match = HTTP_CODE.match(http_code)
    if not match:
        return None
    return match.group('code')


def parse_name(func):
    """
    the func can be

        * undecorated functions
        * decorated functions
        * decorated class methods
    """
    return func.__name__


def default_before_handler(req, resp, req_validation_error, instance):
    """
    default handler called before the endpoint function after the request validation

    :param req: request provided by the web framework
    :param resp: response generated by SpecTree that will be returned
        if the validation error is not None
    :param req_validation_error: request validation error
    :param instance: class instance if the endpoint function is a class method
    """
    if req_validation_error:
        logger.info(
            '422 Validation Error',
            extra={
                'spectree_model': req_validation_error.model.__name__,
                'spectree_validation': req_validation_error.errors(),
            },
        )


def default_after_handler(req, resp, resp_validation_error, instance):
    """
    default handler called after the response validation

    :param req: request provided by the web framework
    :param resp: response from the endpoint function (if there is no validation error)
        or response validation error
    :param resp_validation_error: response validation error
    :param instance: class instance if the endpoint function is a class method
    """
    if resp_validation_error:
        logger.info(
            '500 Response Validation Error',
            extra={
                'spectree_model': resp_validation_error.model.__name__,
                'spectree_validation': resp_validation_error.errors(),
            },
        )
