# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Web clipper for Smart Intake — clip web pages and route through classification pipeline.

Fetches with httpx, extracts text from HTML, saves as Markdown.
Metadata stored in scribe_clips.json.
"""

import json
import logging
import os
import re
import secrets
import threading
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)

_lock = threading.Lock()


def _data_dir() -> Path:
    try:
        from familiar.core.paths import DATA_DIR

        return DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


def _clips_dir() -> Path:
    d = _data_dir() / "clips"
    d.mkdir(parents=True, exist_ok=True)
    return d


def _extract_text_from_html(html: str) -> str:
    """Extract readable text from HTML, stripping tags and boilerplate."""
    # Remove script and style blocks
    text = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r"<nav[^>]*>.*?</nav>", "", text, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r"<footer[^>]*>.*?</footer>", "", text, flags=re.DOTALL | re.IGNORECASE)
    text = re.sub(r"<header[^>]*>.*?</header>", "", text, flags=re.DOTALL | re.IGNORECASE)

    # Convert common block elements to newlines
    text = re.sub(r"<(br|hr|/p|/div|/h[1-6]|/li|/tr)[^>]*>", "\n", text, flags=re.IGNORECASE)
    # Strip remaining tags
    text = re.sub(r"<[^>]+>", " ", text)
    # Decode common HTML entities
    text = text.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">")
    text = text.replace("&quot;", '"').replace("&#39;", "'").replace("&nbsp;", " ")
    # Collapse whitespace
    text = re.sub(r"[ \t]+", " ", text)
    text = re.sub(r"\n\s*\n", "\n\n", text)
    return text.strip()


def _extract_title(html: str) -> str:
    """Extract page title from HTML."""
    match = re.search(r"<title[^>]*>(.*?)</title>", html, re.DOTALL | re.IGNORECASE)
    if match:
        title = match.group(1).strip()
        # Strip tags from title
        title = re.sub(r"<[^>]+>", "", title)
        return title[:200]
    return ""


class WebClipper:
    """Clip web pages and process through intake pipeline."""

    def __init__(self, meta_file: Optional[Path] = None):
        self._file = meta_file or (_data_dir() / "scribe_clips.json")
        self._clips_dir = _clips_dir()
        self._ensure_file()

    def _ensure_file(self):
        if not self._file.exists():
            self._file.parent.mkdir(parents=True, exist_ok=True)
            self._write({"clips": [], "version": 1})

    def _read(self) -> dict:
        try:
            return json.loads(self._file.read_text(encoding="utf-8"))
        except Exception:
            return {"clips": [], "version": 1}

    def _write(self, data: dict):
        tmp = self._file.with_suffix(".tmp")
        tmp.write_text(json.dumps(data, indent=2, default=str), encoding="utf-8")
        os.replace(str(tmp), str(self._file))
        try:
            os.chmod(str(self._file), 0o600)
        except OSError:
            pass

    def clip(self, url: str, dry_run: bool = False) -> dict:
        """Clip a web page: fetch, extract, save, classify, route.

        Returns dict with clip metadata and classification result.
        """
        import httpx

        # Fetch the page
        try:
            resp = httpx.get(
                url,
                timeout=30.0,
                follow_redirects=True,
                headers={"User-Agent": "Familiar-Scribe/1.0 (intake clipper)"},
            )
            resp.raise_for_status()
        except Exception as e:
            return {"error": f"Failed to fetch {url}: {e}", "success": False}

        html = resp.text
        title = _extract_title(html) or url.split("/")[-1] or "untitled"
        text = _extract_text_from_html(html)

        if not text or len(text.strip()) < 50:
            return {"error": "Page has too little text content to clip.", "success": False}

        # Save as Markdown
        clip_id = f"clip_{secrets.token_hex(6)}"
        safe_title = re.sub(r"[^\w\s-]", "", title)[:60].strip().replace(" ", "_") or "clip"
        filename = f"{clip_id}_{safe_title}.md"
        clip_path = self._clips_dir / filename

        md_content = f"# {title}\n\nSource: {url}\nClipped: {datetime.now(timezone.utc).isoformat()}\n\n---\n\n{text}"

        if not dry_run:
            clip_path.write_text(md_content, encoding="utf-8")

        # Classify through pipeline
        classification = None
        route_result = None
        try:
            from familiar.skills.intake.classifiers import classify

            classification = classify(text[:5000], filepath=str(clip_path))

            if not dry_run:
                from familiar.skills.intake.router import route

                route_result = route(classification, dry_run=False)
        except Exception as e:
            logger.debug("Clip classification failed: %s", e)

        # Record metadata
        meta = {
            "id": clip_id,
            "url": url,
            "title": title,
            "filename": filename,
            "filepath": str(clip_path),
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "text_length": len(text),
            "content_type": classification.content_type.value if classification else "unknown",
            "confidence": classification.confidence if classification else 0.0,
            "destination": route_result.destination if route_result else "",
            "success": route_result.success if route_result else False,
            "dry_run": dry_run,
        }

        if not dry_run:
            with _lock:
                data = self._read()
                data["clips"].insert(0, meta)
                data["clips"] = data["clips"][:500]
                self._write(data)

            # Record in intake history
            try:
                from familiar.skills.intake.history import get_intake_history

                history = get_intake_history()
                history.record(
                    source_file=url,
                    content_type=meta["content_type"],
                    confidence=meta["confidence"],
                    method="web_clip",
                    destination=meta["destination"],
                    success=meta["success"],
                    summary=f"Web clip: {title}",
                    text_preview=text[:500],
                )
            except Exception as e:
                logger.debug("Failed to record clip in history: %s", e)

        meta["text_preview"] = text[:300]
        return meta

    def list_clips(self, limit: int = 50) -> list[dict]:
        """List saved clips."""
        data = self._read()
        return data.get("clips", [])[:limit]

    def get_clip(self, clip_id: str) -> Optional[dict]:
        """Get a specific clip by ID."""
        data = self._read()
        for clip in data.get("clips", []):
            if clip.get("id") == clip_id:
                return clip
        return None


_instance: Optional[WebClipper] = None


def get_web_clipper() -> WebClipper:
    """Singleton accessor."""
    global _instance
    if _instance is None:
        _instance = WebClipper()
    return _instance
