from __future__ import annotations

import ipaddress
import re
from typing import Dict, List, Mapping, Optional


def parse_cookie_header(cookie: str) -> Dict[str, str]:
    raw = cookie or ""
    out: Dict[str, str] = {}
    for part in raw.split(";"):
        if "=" not in part:
            continue
        k, v = part.split("=", 1)
        k = k.strip()
        v = v.strip()
        if k:
            out[k] = v
    return out


def normalize_ip(ip: str) -> str:
    if not ip:
        return ""
    s = str(ip).strip()
    if s.startswith("::ffff:"):
        s = s[len("::ffff:") :]
    if "%" in s:
        s = s.split("%", 1)[0]
    try:
        return str(ipaddress.ip_address(s))
    except Exception:
        return ""


def detect_country(headers: Mapping[str, str]) -> str:
    cf = headers.get("cf-ipcountry")
    if cf:
        return str(cf).upper()[:2]
    keys = [
        "x-country-code",
        "x-azure-geo-country-pc",
        "x-appengine-country",
        "geoip-country-code",
        "x-geo-country",
        "ci-geoip-country-code",
    ]
    for k in keys:
        v = headers.get(k)
        if v:
            return str(v).upper()[:2]
    return "XX"


def is_static_request(uri: str) -> bool:
    p = (uri or "").split("?", 1)[0]
    if "." not in p:
        return False
    ext = p.rsplit(".", 1)[-1].lower()
    if not ext:
        return False
    static_exts = {
        "css",
        "js",
        "map",
        "png",
        "jpg",
        "jpeg",
        "gif",
        "webp",
        "svg",
        "ico",
        "woff",
        "woff2",
        "ttf",
        "eot",
        "otf",
        "mp3",
        "mp4",
        "webm",
        "wav",
        "ogg",
        "pdf",
        "zip",
        "gz",
        "rar",
    }
    return ext in static_exts


def is_sensitive_request(method: str, uri: str) -> bool:
    m = (method or "").upper()
    if m == "POST":
        return True
    path = (uri or "").split("?", 1)[0].lower()
    markers = ["/wp-login", "/wp-admin", "/xmlrpc", "/login", "/admin", "/checkout"]
    return any(x in path for x in markers)


def should_protect_request(*, path: str, only_paths: Optional[List[str]], except_paths: Optional[List[str]], only_regex: Optional[str]) -> bool:
    p = path or "/"

    def match_list(patterns: List[str]) -> bool:
        for pat in patterns:
            s = str(pat or "")
            if not s:
                continue
            if s.endswith("*"):
                prefix = s[:-1].rstrip("/")
                if prefix and p.startswith(prefix):
                    return True
            elif p == s:
                return True
        return False

    if except_paths and match_list(except_paths):
        return False
    if only_paths and only_paths:
        return match_list(only_paths)
    if only_regex:
        try:
            return re.search(only_regex, p) is not None
        except Exception:
            return True
    return True


def cidr_match(ip: str, cidr: str) -> bool:
    try:
        return ipaddress.ip_address(ip) in ipaddress.ip_network(cidr, strict=False)
    except Exception:
        return False


def compile_php_like_regex(raw: str) -> Optional[re.Pattern]:
    s = str(raw or "")
    if not s:
        return None

    delim = s[0]
    if delim.isalnum() or delim == "\\":
        try:
            return re.compile(s)
        except Exception:
            return None

    last = _find_last_unescaped(s, delim)
    if last <= 0:
        try:
            return re.compile(s)
        except Exception:
            return None

    pattern = s[1:last]
    flags_raw = s[last + 1 :]
    flags = 0
    if "i" in flags_raw:
        flags |= re.IGNORECASE
    if "m" in flags_raw:
        flags |= re.MULTILINE
    if "s" in flags_raw:
        flags |= re.DOTALL

    try:
        return re.compile(pattern, flags)
    except Exception:
        return None


def _find_last_unescaped(s: str, ch: str) -> int:
    for i in range(len(s) - 1, 0, -1):
        if s[i] != ch:
            continue
        # count preceding backslashes
        bs = 0
        j = i - 1
        while j >= 0 and s[j] == "\\":
            bs += 1
            j -= 1
        if bs % 2 == 0:
            return i
    return -1
