import inspect
import logging
import re

# 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)
    """
    attr_to_spec_key = {"query": "query", "headers": "header", "cookies": "cookie"}
    route_param_keywords = ("explode", "style", "allowReserved")

    for attr in attr_to_spec_key:
        if hasattr(func, attr):
            model = models[getattr(func, attr)]
            for name, schema in model["properties"].items():
                # Route parameters keywords taken out of schema level
                extra = {
                    kw: schema.pop(kw) for kw in route_param_keywords if kw in schema
                }
                params.append(
                    {
                        "name": name,
                        "in": attr_to_spec_key[attr],
                        "schema": schema,
                        "required": name in model.get("required", []),
                        "description": schema.get("description", ""),
                        **extra,
                    }
                )

    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()

    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

    return bool(hasattr(func, "resp") and func.resp.has_model())


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(),
            },
        )
