"""Helpers for no-cost content extraction from URLs, files, and simple media sources."""

from __future__ import annotations

import base64
import io
import json
import mimetypes
import os
import re
import xml.etree.ElementTree as ET
from html import unescape
from pathlib import Path
from urllib.parse import parse_qs, urlparse

import requests
import urllib3

from backend.config import BS4_SUPPORT, PDF_SUPPORT, pypdf

try:
    from backend.config import BeautifulSoup
except ImportError:
    BeautifulSoup = None


_TEXT_EXTENSIONS = {
    ".txt", ".md", ".json", ".yaml", ".yml", ".xml", ".csv", ".log", ".ini",
    ".cfg", ".toml", ".py", ".js", ".ts", ".tsx", ".jsx", ".html", ".css",
    ".sql", ".ps1", ".bat", ".sh", ".srt", ".vtt",
}
_SIDECAR_EXTENSIONS = (".txt", ".md", ".srt", ".vtt")

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def _limit_text(value: str, max_chars: int) -> str:
    text = (value or "").strip()
    if len(text) <= max_chars:
        return text
    return text[: max_chars - 1].rstrip() + "…"


def _looks_like_url(value: str) -> bool:
    parsed = urlparse(value.strip())
    return parsed.scheme in {"http", "https"} and bool(parsed.netloc)


def _http_get(url: str, timeout: int = 20) -> requests.Response:
    headers = {"User-Agent": "Mozilla/5.0"}
    try:
        return requests.get(url, timeout=timeout, headers=headers)
    except requests.exceptions.SSLError:
        return requests.get(url, timeout=timeout, headers=headers, verify=False)


def _is_youtube_url(url: str) -> bool:
    host = (urlparse(url).netloc or "").lower()
    return host in {"youtube.com", "www.youtube.com", "m.youtube.com", "youtu.be"}


def _extract_youtube_id(url: str) -> str:
    parsed = urlparse(url)
    host = (parsed.netloc or "").lower()
    if host == "youtu.be":
        return parsed.path.strip("/")
    if parsed.path == "/watch":
        return (parse_qs(parsed.query).get("v") or [""])[0]
    parts = [part for part in parsed.path.split("/") if part]
    if len(parts) >= 2 and parts[0] in {"embed", "shorts", "live"}:
        return parts[1]
    return ""


def _clean_html_text(html: str, max_chars: int) -> str:
    if BS4_SUPPORT and BeautifulSoup:
        soup = BeautifulSoup(html, "html.parser")
        for tag in soup(["script", "style", "nav", "footer", "noscript"]):
            tag.decompose()
        text = soup.get_text(separator="\n", strip=True)
    else:
        text = re.sub(r"<[^>]+>", "\n", html)
    text = re.sub(r"\n{3,}", "\n\n", text)
    return _limit_text(text, max_chars)


def _extract_pdf_text(data: bytes, filename: str, max_chars: int) -> str:
    if not (PDF_SUPPORT and pypdf):
        return f"[PDF: {filename}]\nPDF extraction is unavailable because pypdf is not installed."
    try:
        reader = pypdf.PdfReader(io.BytesIO(data))
        text = "\n".join(page.extract_text() or "" for page in reader.pages)
        text = text.strip()
        if not text:
            return f"[PDF: {filename}]\nNo extractable text found in the PDF."
        return f"[PDF: {filename}]\n{_limit_text(text, max_chars)}"
    except Exception as exc:
        return f"[PDF: {filename}]\nPDF extraction error: {exc}"


def _strip_caption_text(text: str) -> str:
    cleaned = text.replace("\ufeff", "")
    cleaned = re.sub(r"^WEBVTT\s*", "", cleaned, flags=re.IGNORECASE)
    cleaned = re.sub(r"\d{2}:\d{2}:\d{2}[.,]\d{3}\s*-->\s*\d{2}:\d{2}:\d{2}[.,]\d{3}", "", cleaned)
    cleaned = re.sub(r"\d{2}:\d{2}[.,]\d{3}\s*-->\s*\d{2}:\d{2}[.,]\d{3}", "", cleaned)
    cleaned = re.sub(r"^\d+$", "", cleaned, flags=re.MULTILINE)
    cleaned = re.sub(r"<[^>]+>", "", cleaned)
    cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
    return cleaned.strip()


def _extract_image_note(filename: str, mime_type: str, size_bytes: int) -> str:
    return (
        f"[IMAGE: {filename}]\n"
        f"Mime: {mime_type or 'application/octet-stream'}\n"
        f"Size: {size_bytes} bytes\n"
        "No-cost OCR is not available in this build. If you need image understanding, use a vision-capable model or attach a text transcript/description alongside the image."
    )


def _extract_media_note(filename: str, mime_type: str, size_bytes: int) -> str:
    return (
        f"[MEDIA: {filename}]\n"
        f"Mime: {mime_type or 'application/octet-stream'}\n"
        f"Size: {size_bytes} bytes\n"
        "No-cost transcription for raw audio/video is only supported when a local transcript exists (.txt/.md/.srt/.vtt) or when the source publishes captions/transcripts."
    )


def _read_sidecar_transcript(path: Path, max_chars: int) -> str | None:
    stem = path.with_suffix("")
    for ext in _SIDECAR_EXTENSIONS:
        candidate = stem.with_suffix(ext)
        if not candidate.exists() or not candidate.is_file():
            continue
        try:
            text = candidate.read_text(encoding="utf-8", errors="replace")
            if ext in {".srt", ".vtt"}:
                text = _strip_caption_text(text)
            text = text.strip()
            if text:
                return (
                    f"[TRANSCRIPT: {candidate.name}]\n"
                    f"Source file: {path.name}\n"
                    f"{_limit_text(text, max_chars)}"
                )
        except OSError:
            continue
    return None


def _extract_text_bytes(data: bytes, filename: str, max_chars: int) -> str:
    try:
        text = data.decode("utf-8", errors="replace")
    except Exception as exc:
        return f"[FILE: {filename}]\nCould not decode text: {exc}"
    if filename.lower().endswith((".srt", ".vtt")):
        text = _strip_caption_text(text)
    return f"[FILE: {filename}]\n{_limit_text(text, max_chars)}"


def extract_uploaded_bytes(data: bytes, filename: str, mime_type: str = "", max_chars: int = 50000) -> str:
    lower_name = (filename or "upload").lower()
    mime = (mime_type or "").lower()
    if "pdf" in mime or lower_name.endswith(".pdf"):
        return _extract_pdf_text(data, filename, max_chars=max_chars)
    if mime.startswith("image/"):
        b64 = base64.b64encode(data).decode()
        return (
            f"[IMAGE: {filename}]\n"
            f"[base64:{mime};{b64}]"
        )
    if mime.startswith("audio/") or mime.startswith("video/"):
        return _extract_media_note(filename, mime, len(data))
    return _extract_text_bytes(data, filename, max_chars=max_chars)


def _fetch_caption_text(caption_url: str, max_chars: int) -> str:
    response = _http_get(caption_url, timeout=15)
    response.raise_for_status()
    text = response.text
    if "<transcript" in text:
        root = ET.fromstring(text)
        parts = [unescape((node.text or "").strip()) for node in root.findall(".//text")]
        joined = "\n".join(part for part in parts if part)
    else:
        joined = _strip_caption_text(text)
    return _limit_text(joined, max_chars)


def _extract_youtube_content(url: str, html: str, max_chars: int) -> str:
    title_match = re.search(r'<meta[^>]+property="og:title"[^>]+content="([^"]+)"', html, flags=re.IGNORECASE)
    desc_match = re.search(r'<meta[^>]+property="og:description"[^>]+content="([^"]+)"', html, flags=re.IGNORECASE)
    title = unescape(title_match.group(1)) if title_match else "YouTube video"
    description = unescape(desc_match.group(1)) if desc_match else ""

    caption_match = re.search(r'"baseUrl":"([^"]*timedtext[^"]*)"', html)
    if caption_match:
        caption_url = caption_match.group(1)
        caption_url = bytes(caption_url, "utf-8").decode("unicode_escape")
        caption_url = caption_url.replace("\\/", "/").replace("\u0026", "&")
        if caption_url.startswith("//"):
            caption_url = "https:" + caption_url
        try:
            transcript = _fetch_caption_text(caption_url, max_chars=max_chars)
            if transcript:
                return (
                    f"[YOUTUBE: {title}]\n"
                    f"URL: {url}\n"
                    f"Description: {description}\n\n"
                    f"Transcript:\n{transcript}"
                )
        except Exception as exc:
            return (
                f"[YOUTUBE: {title}]\nURL: {url}\nDescription: {description}\n\n"
                f"Transcript fetch failed: {exc}"
            )

    return (
        f"[YOUTUBE: {title}]\nURL: {url}\nDescription: {description or 'No public transcript found.'}\n\n"
        "No free public transcript was found for this video."
    )


def _parse_podcast_feed(xml_text: str, source_url: str, max_chars: int) -> str:
    root = ET.fromstring(xml_text)
    channel = root.find("channel")
    item = channel.find("item") if channel is not None else None
    if item is None:
        entry = root.find("{http://www.w3.org/2005/Atom}entry")
        item = entry
    if item is None:
        return _limit_text(_clean_html_text(xml_text, max_chars), max_chars)

    title = (item.findtext("title") or channel.findtext("title") or "Podcast episode").strip() if channel is not None else (item.findtext("title") or "Podcast episode").strip()
    description = (
        item.findtext("description")
        or item.findtext("{http://www.itunes.com/dtds/podcast-1.0.dtd}summary")
        or ""
    ).strip()
    transcript_url = ""
    for element in item.iter():
        if element.tag.endswith("transcript") and element.attrib.get("url"):
            transcript_url = element.attrib["url"].strip()
            break
    if transcript_url:
        try:
            transcript_resp = _http_get(transcript_url, timeout=15)
            transcript_resp.raise_for_status()
            transcript_text = transcript_resp.text
            if transcript_url.lower().endswith((".srt", ".vtt")):
                transcript_text = _strip_caption_text(transcript_text)
            transcript_text = _limit_text(_clean_html_text(transcript_text, max_chars), max_chars)
            return (
                f"[PODCAST: {title}]\nSource: {source_url}\nSummary: {description}\n\n"
                f"Transcript:\n{transcript_text}"
            )
        except Exception as exc:
            return f"[PODCAST: {title}]\nSource: {source_url}\nSummary: {description}\n\nTranscript fetch failed: {exc}"

    return f"[PODCAST: {title}]\nSource: {source_url}\nSummary: {_limit_text(description, max_chars)}"


def _extract_url(url: str, max_chars: int) -> str:
    response = _http_get(url, timeout=20)
    response.raise_for_status()
    content_type = (response.headers.get("content-type") or "").lower()
    final_url = response.url

    if _is_youtube_url(final_url):
        return _extract_youtube_content(final_url, response.text, max_chars=max_chars)
    if "pdf" in content_type or final_url.lower().endswith(".pdf"):
        return _extract_pdf_text(response.content, Path(urlparse(final_url).path).name or "document.pdf", max_chars=max_chars)
    if content_type.startswith("image/"):
        name = Path(urlparse(final_url).path).name or "image"
        return _extract_image_note(name, content_type, len(response.content))
    if content_type.startswith("audio/") or content_type.startswith("video/"):
        name = Path(urlparse(final_url).path).name or "media"
        return _extract_media_note(name, content_type, len(response.content))
    if "xml" in content_type or response.text.lstrip().startswith("<?xml") or "<rss" in response.text[:500].lower():
        try:
            return _parse_podcast_feed(response.text, final_url, max_chars=max_chars)
        except Exception:
            pass
    return f"[URL: {final_url}]\n{_clean_html_text(response.text, max_chars)}"


def _extract_local_path(path: Path, max_chars: int) -> str:
    if not path.exists() or not path.is_file():
        return f"Path not found: {path}"
    mime, _ = mimetypes.guess_type(str(path))
    lower_suffix = path.suffix.lower()
    data = path.read_bytes()

    if lower_suffix == ".pdf":
        return _extract_pdf_text(data, path.name, max_chars=max_chars)
    if lower_suffix in {".srt", ".vtt"}:
        return f"[TRANSCRIPT: {path.name}]\n{_limit_text(_strip_caption_text(data.decode('utf-8', errors='replace')), max_chars)}"
    if lower_suffix in _TEXT_EXTENSIONS or (mime or "").startswith("text/"):
        return _extract_text_bytes(data, path.name, max_chars=max_chars)
    if (mime or "").startswith("image/"):
        return _extract_image_note(path.name, mime or "", len(data))
    if (mime or "").startswith(("audio/", "video/")) or lower_suffix in {".mp3", ".wav", ".m4a", ".mp4", ".mov", ".webm", ".mkv"}:
        transcript = _read_sidecar_transcript(path, max_chars=max_chars)
        if transcript:
            return transcript
        return _extract_media_note(path.name, mime or "", len(data))
    return f"[FILE: {path.name}]\nBinary file type is not supported for free extraction in this build."


def extract_source(source: str, max_chars: int = 24000) -> str:
    target = str(source or "").strip()
    if not target:
        return "No source provided."
    try:
        if _looks_like_url(target):
            return _extract_url(target, max_chars=max_chars)
        return _extract_local_path(Path(target), max_chars=max_chars)
    except Exception as exc:
        return f"Could not extract content from '{target}': {exc}"