from bs4 import BeautifulSoup,Tag

from urllib.parse import urlparse, urlsplit, urlunsplit, unquote, quote, urljoin

from page_scraper.entities.models import UrlContext

import requests

SAFE_PATH="/-_.~@"

CONTENT_TYPE_MAP = {
    "application/ld+json": "json",
    "application/json": "json",
    "application/pdf": "pdf",
    "text/css": "stylesheet",
    "text/javascript": "javascript",
    "text/html": "html",
    "text/xml": "xml",
}

def is_root_url(url:str) -> bool:
    parsed = urlparse(url)
    return parsed.path in ("", "/")

def is_domain(node: dict) -> bool:
    url = node.get("url")
    return bool(url) and is_root_url(url)

def process_breadcrumbs(node:dict):
    breadcrumbs = []
    for li in node.get("itemListElement", []):
        item = li.get("item")
        crumb = {
            "position": li.get("position"),
            "name": li.get("name") or (item.get("name") if isinstance(item, dict) else None),
            "url": item.get("@id") if isinstance(item, dict) else item,
        }
        breadcrumbs.append(crumb)

    # sort explicitly (VERY IMPORTANT)
    breadcrumbs.sort(key=lambda x: x["position"] or 0)
    return breadcrumbs


def infer_categories(breadcrumbs):
    if len(breadcrumbs) <= 1:
        return []

    drop_first = is_domain(breadcrumbs[0])

    start = 1 if drop_first else 0

    return breadcrumbs[start:-1]

def clear_text(text:str) -> str | None:
    return text.strip().replace("\n", " ") or None

def process_text(tag:Tag)->str:
    return clear_text(tag.get_text())

def canonical_url(url: str, base:str) -> str:
    url = urljoin(base,url)
    parts = urlsplit(url)

    path = unquote(parts.path)
    path = quote(path, safe=SAFE_PATH)

    query = unquote(parts.query)
    query = quote(query, safe="=&")

    return urlunsplit((
        parts.scheme.lower(),
        parts.netloc.lower(),
        path,
        query,
        ""  # drop fragment for canonical URLs
    ))

def canonical_domain(url: str) -> str:
    split_url = urlsplit(url)
    return f"{split_url.scheme}://{split_url.netloc}"

def exclude_tags(soup: BeautifulSoup, selector: str) -> None:
    for tag in soup.select(selector):
        tag.decompose()

def get_page_type(content_type: str | None) -> str | None:
    if not content_type:
        return None

    ct = content_type.lower()

    # exact / substring matches first
    for key, value in CONTENT_TYPE_MAP.items():
        if key in ct:
            return value

    # prefix matches
    if ct.startswith("audio/"):
        return "podcast"
    if ct.startswith("image/"):
        return "image"
    if ct.startswith("video/"):
        return "video"

    return None

def process_link(current:str,response: requests.Response | None = None, url_type:str | None = None) -> UrlContext:
    status_code = 0
    content_type = url_type
    if response:
        status_code = response.status_code
        content_type = get_page_type(response.headers.get("content-type")),

    return UrlContext(
        url=current,
        status_code=status_code,
        content_type=content_type,
    )

def classify_url(url: str) -> str:
    parsed = urlsplit(url)

    if parsed.scheme == "mailto":
        return "email"
    if parsed.scheme in {"tel", "sms"}:
        return "phone"
    if parsed.scheme in {"whatsapp", "skype"}:
        return "messaging"
    if not parsed.scheme or parsed.scheme in ("http", "https"):
        return "web"

    return "other"