"""Atom feed generation for foliate."""

from dataclasses import dataclass
from datetime import date, datetime, timedelta, timezone
from html import escape
from pathlib import Path
from typing import TYPE_CHECKING, Optional

from bs4 import BeautifulSoup

# Re-export FeedConfig from config for convenience
from .config import FeedConfig
from .markdown_utils import render_markdown

if TYPE_CHECKING:
    from jinja2 import Environment

    from .config import Config

__all__ = ["FeedConfig", "FeedItem", "generate_feed"]


@dataclass
class FeedItem:
    """A single item in a feed."""

    title: str
    url: str
    content: str
    published: datetime
    updated: datetime
    summary: Optional[str] = None


def parse_frontmatter_date(value) -> Optional[datetime]:
    """Parse a frontmatter date value to datetime.

    Accepts:
        - ISO 8601 date string: "2024-03-15"
        - ISO 8601 datetime string: "2024-03-15T10:30:00"
        - With timezone: "2024-03-15T10:30:00+09:00"
        - date object
        - datetime object

    Returns:
        datetime in UTC, or None if parsing fails
    """
    if value is None:
        return None

    if isinstance(value, datetime):
        if value.tzinfo is None:
            return value.replace(tzinfo=timezone.utc)
        return value.astimezone(timezone.utc)

    if isinstance(value, date) and not isinstance(value, datetime):
        return datetime(value.year, value.month, value.day, tzinfo=timezone.utc)

    if not isinstance(value, str):
        return None

    # Try parsing as ISO date/datetime
    try:
        if "T" in value:
            # Datetime - normalize Z to +00:00 for fromisoformat
            dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
            if dt.tzinfo is None:
                # No timezone info, assume UTC
                dt = dt.replace(tzinfo=timezone.utc)
            return dt.astimezone(timezone.utc)
        else:
            # Date only
            d = date.fromisoformat(value)
            return datetime(d.year, d.month, d.day, tzinfo=timezone.utc)
    except (ValueError, TypeError):
        return None


def get_published_date(page: dict) -> Optional[datetime]:
    """Extract publication date from page.

    Resolution order:
        1. published field as date (not boolean True)
        2. date field
        3. file modification time (fallback)
    """
    meta = page.get("meta", {})

    # Check published field (if it's a date, not boolean)
    published = meta.get("published")
    if published is not None and published is not True and published is not False:
        result = parse_frontmatter_date(published)
        if result:
            return result

    # Check date field
    date_val = meta.get("date")
    if date_val:
        result = parse_frontmatter_date(date_val)
        if result:
            return result

    # Fallback to file mtime
    file_mtime = page.get("file_mtime")
    if file_mtime:
        try:
            return datetime.fromtimestamp(file_mtime, tz=timezone.utc)
        except (OSError, OverflowError, ValueError):
            pass

    return None


def get_modified_date(page: dict) -> Optional[datetime]:
    """Extract modification date from page.

    Resolution order:
        1. modified field in frontmatter
        2. file modification time
        3. published date (fallback)
    """
    meta = page.get("meta", {})

    # Check modified field
    modified = meta.get("modified")
    if modified:
        result = parse_frontmatter_date(modified)
        if result:
            return result

    # Fallback to file mtime
    file_mtime = page.get("file_mtime")
    if file_mtime:
        try:
            return datetime.fromtimestamp(file_mtime, tz=timezone.utc)
        except (OSError, OverflowError, ValueError):
            pass

    # Final fallback to published date
    return get_published_date(page)


def format_atom_date(dt: datetime) -> str:
    """Format datetime as RFC 3339 for Atom.

    Args:
        dt: datetime object (should be in UTC)

    Returns:
        RFC 3339 formatted string like "2024-03-15T10:30:00Z"
    """
    return dt.strftime("%Y-%m-%dT%H:%M:%SZ")


def classify_pages(
    pages: list[dict],
    window_days: int,
    now: Optional[datetime] = None,
) -> tuple[list[dict], list[dict]]:
    """Classify pages into 'new' and 'updated' categories.

    Args:
        pages: List of page dictionaries
        window_days: Number of days to look back
        now: Current datetime (for testing), defaults to now

    Returns:
        (new_pages, updated_pages) - both sorted by date descending
    """
    if now is None:
        now = datetime.now(timezone.utc)

    window_start = now - timedelta(days=window_days)
    new_pages = []
    updated_pages = []

    for page in pages:
        published = get_published_date(page)
        modified = get_modified_date(page)

        if published is None:
            continue

        # Is the page "new" (published within window)?
        is_new = published >= window_start

        if is_new:
            new_pages.append(page)
        elif modified and modified >= window_start:
            # Page was published before window but modified within
            updated_pages.append(page)
        # else: page is outside window entirely, skip

    # Sort by date descending, then by path ascending for deterministic order
    new_pages.sort(
        key=lambda p: (-(get_published_date(p) or now).timestamp(), p["path"])
    )
    updated_pages.sort(
        key=lambda p: (-(get_modified_date(p) or now).timestamp(), p["path"])
    )

    return new_pages, updated_pages


def extract_summary(html: str, max_length: int = 300) -> str:
    """Extract a plain text summary from HTML.

    Extracts the first paragraph and truncates if needed.

    Args:
        html: HTML content
        max_length: Maximum length of summary

    Returns:
        Plain text summary
    """
    if not html:
        return ""

    soup = BeautifulSoup(html, "html.parser")

    # Find first paragraph
    first_p = soup.find("p")
    if first_p:
        text = first_p.get_text()
    else:
        text = soup.get_text()

    text = text.strip()

    if len(text) > max_length:
        return text[:max_length] + "..."

    return text


def generate_updates_digest(pages: list[dict], site_url: str) -> str:
    """Generate HTML content for the updates digest entry.

    Args:
        pages: List of updated pages
        site_url: Base URL of the site

    Returns:
        HTML string with list of updated pages
    """
    if not pages:
        return ""

    lines = ["<p>The following pages were recently updated:</p>", "<ul>"]

    for page in pages:
        title = escape(page.get("title", page["path"]))
        url = escape(f"{site_url}{page['url']}", quote=True)
        modified = get_modified_date(page)
        date_str = modified.strftime("%Y-%m-%d") if modified else ""

        lines.append(f'  <li><a href="{url}">{title}</a> - {date_str}</li>')

    lines.append("</ul>")
    return "\n".join(lines)


def create_feed_items(
    pages: list[dict],
    site_url: str,
    full_content: bool,
    max_items: int,
) -> list[FeedItem]:
    """Create FeedItem objects from pages.

    Args:
        pages: List of page dictionaries
        site_url: Base URL of the site
        full_content: Whether to include full content or summary
        max_items: Maximum number of items to include

    Returns:
        List of FeedItem objects
    """
    items = []

    for page in pages[:max_items]:
        published = get_published_date(page)
        modified = get_modified_date(page) or published

        if not published:
            continue

        url = f"{site_url}{page['url']}"
        content = page.get("html", "")
        if not content and page.get("body"):
            content = render_markdown(
                page["body"],
                page.get("base_url", "/wiki/"),
            )

        summary = extract_summary(content) if not full_content else None

        item = FeedItem(
            title=page.get("title", page["path"]),
            url=url,
            content=content if full_content else "",
            published=published,
            updated=modified,
            summary=summary,
        )
        items.append(item)

    return items


def generate_feed(
    pages: list[dict],
    config: "Config",
    templates: "Environment",
    output_dir: Path,
) -> None:
    """Generate Atom feed with new pages and updates digest.

    Args:
        pages: List of published page dictionaries
        config: Site configuration
        templates: Jinja2 environment
        output_dir: Build output directory
    """
    from . import __version__

    feed_config = config.feed
    if not feed_config.enabled:
        return

    site_url = config.site.url.rstrip("/")
    now = datetime.now(timezone.utc)

    # Filter out homepage content (only include wiki pages)
    wiki_prefix = config.build.wiki_prefix.strip("/")
    wiki_url_prefix = f"/{wiki_prefix}/" if wiki_prefix else "/"

    # Only include pages whose URL starts with wiki prefix
    # This excludes _homepage/ content which goes to /
    wiki_pages = [p for p in pages if p.get("url", "").startswith(wiki_url_prefix)]

    # If wiki prefix is empty, we can't distinguish - include all pages
    # Otherwise, only include wiki pages
    if wiki_prefix:
        feed_pages = wiki_pages
    else:
        feed_pages = pages

    # Classify pages
    new_pages, updated_pages = classify_pages(feed_pages, feed_config.window, now)

    # If no pages in either category, remove stale feed and skip generation
    if not new_pages and not updated_pages:
        feed_file = output_dir / "feed.xml"
        if feed_file.exists():
            feed_file.unlink()
        return

    # Create feed items for new pages
    new_items = create_feed_items(
        new_pages,
        site_url,
        feed_config.full_content,
        feed_config.items,
    )

    # Create updates digest entry if there are updated pages
    updates_entry = None
    if updated_pages:
        digest_content = generate_updates_digest(updated_pages, site_url)
        most_recent_update = get_modified_date(updated_pages[0])
        updates_entry = {
            "content": digest_content,
            "updated": format_atom_date(most_recent_update),
        }

    # Determine feed metadata
    feed_title = feed_config.title or config.site.name
    feed_description = feed_config.description or f"{config.site.name} - Recent updates"

    # Determine feed updated time (most recent of all entries)
    all_dates = []
    if new_items:
        all_dates.extend(item.updated for item in new_items)
    if updates_entry and updated_pages:
        all_dates.append(get_modified_date(updated_pages[0]))
    feed_updated = max(all_dates) if all_dates else now

    # Format items for template
    formatted_items = [
        {
            "title": item.title,
            "url": item.url,
            "published": format_atom_date(item.published),
            "updated": format_atom_date(item.updated),
            "content": item.content,
            "summary": item.summary,
        }
        for item in new_items
    ]

    # Render feed template
    try:
        template = templates.get_template("feed.xml")
    except Exception as e:
        from .logging import warning

        warning(f"Failed to load feed template: {e}")
        warning("Feed generation skipped. Ensure feed.xml template exists.")
        return
    wiki_url = f"{site_url}/{wiki_prefix}/" if wiki_prefix else f"{site_url}/"

    feed_xml = template.render(
        config=config,
        feed_title=feed_title,
        feed_description=feed_description,
        feed_updated=format_atom_date(feed_updated),
        new_items=formatted_items,
        updates_entry=updates_entry,
        full_content=feed_config.full_content,
        version=__version__,
        wiki_url=wiki_url,
    )

    # Write feed file
    (output_dir / "feed.xml").write_text(feed_xml, encoding="utf-8")
