import inspect
from itertools import chain
from typing import Any
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional

from ddtrace._trace.span import Span
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
from ddtrace.settings.asm import config as asm_config
import ddtrace.tracer


# remove_top default value of 8
# report_stack/_waf_action/waf_callable/call_waf_callback
# wrapped_open/rasp/_wrap_call/patch_func


def report_stack(
    message: str, span: Optional[Span] = None, crop_stack: Optional[str] = None, stack_id: Optional[str] = None
):
    """
    Report a stack trace to the current span.
    This is used to report stack traces for exploit prevention.
    Return the stack id for the reported stack trace to link it in triggers.
    """
    if not asm_config._ep_enabled or not asm_config._ep_stack_trace_enabled:
        return None
    if span is None:
        span = ddtrace.tracer.current_span()
    if span is None or stack_id is None:
        return None
    root_span = span._local_root or span
    appsec_traces = root_span.get_struct_tag(EXPLOIT_PREVENTION.STACK_TRACES) or {}
    exploit: List[Any] = appsec_traces.get("exploit", [])

    # Do not report more than the maximum number of stack traces
    if asm_config._ep_max_stack_traces and len(exploit) >= asm_config._ep_max_stack_traces:
        return None

    stack = inspect.stack()
    if crop_stack is not None:
        for i, frame in enumerate(stack):
            if stack[i].frame.f_code.co_name == crop_stack:
                stack = stack[i + 1 :]
                break
    res: Dict[str, Any] = {
        "language": "python",
        "id": stack_id,
        "message": message,
    }
    if asm_config._ep_max_stack_trace_depth and len(stack) > asm_config._ep_max_stack_trace_depth:
        top_stack = asm_config._ep_max_stack_trace_depth // 4
        bottom_stack = asm_config._ep_max_stack_trace_depth - top_stack
        iterator: Iterable[int] = chain(range(top_stack), range(len(stack) - bottom_stack, len(stack)))
    else:
        iterator = range(len(stack))
    frames = [
        {
            "id": i,
            "function": getattr(stack[i].frame.f_code, "co_qualname", stack[i].frame.f_code.co_name),
            "file": stack[i].filename,
            "line": stack[i].lineno,
        }
        for i in iterator
    ]
    res["frames"] = frames
    exploit.append(res)
    appsec_traces["exploit"] = exploit
    root_span.set_struct_tag(EXPLOIT_PREVENTION.STACK_TRACES, appsec_traces)
