"""The plain-text and syndication surfaces: the RSS feed and llms.txt.

Both are read by machines that will never run the app, so both are built
straight from the database at request time, carry absolute URLs from the
configured origin, and state nothing that is not counted. The feed's items are
the products most recently added to the catalogue at a visible airport and the
Professor's published articles (Stream D's table), one channel, newest first,
through `FeedItem`. llms.txt is the site in the shape an assistant reads
first: what it is, how to read a price, where the pages and the machine
surfaces are, with the live airport list and counts.
"""

import html
from dataclasses import dataclass
from datetime import UTC, datetime
from email.utils import format_datetime

from sqlalchemy.orm import Session

from app.models.editorial import ArticleOut
from app.models.hubs import AirportSummary, DatasetFacts
from app.models.schemas import CategoryCount, ProductSummary
from app.services import catalog_queries, editorial
from app.services.seo import fmt_date, fmt_size
from app.services.urls import category_path, product_path

SITE_NAME = "Duty Free Professor"
FEED_TITLE = f"{SITE_NAME}: new in duty free"
FEED_DESCRIPTION = (
    "Products newly added to the catalogue of airport duty-free prices we track, "
    "with where they are stocked and the dated prices we observed, and the Professor's articles."
)
FEED_ITEMS = 50
#: Articles are few and hand-written, so every recent one rides along however many
#: products a collection added the same day.
FEED_ARTICLES = 20


@dataclass(frozen=True)
class FeedItem:
    title: str
    path: str
    published: datetime
    description: str
    #: Stable identity for the reader; the path is one for a product or an article.
    guid: str


def product_item(product: ProductSummary, added: datetime) -> FeedItem:
    """One product as a feed item: facts about where it is priced, never copy."""
    bits = [b for b in (product.brand, product.category, fmt_size(product.quantity_ml)) if b]
    shop = product.best_shop_iata or product.best_shop
    if product.shop_count > 1:
        where = f"Priced at {product.shop_count} airport shops"
        if product.cheapest_usd is not None and product.dearest_usd is not None:
            where += f", from ${product.cheapest_usd:,.2f} to ${product.dearest_usd:,.2f}"
        if shop:
            where += f"; cheapest at {shop}"
    else:
        where = f"Priced at {shop}" if shop else "Priced at one airport shop"
        if product.cheapest_usd is not None:
            where += f": ${product.cheapest_usd:,.2f}"
    description = ". ".join(filter(None, [", ".join(bits), where])) + ". Every price is dated on the page."
    # The link is the card's own address (the product line with this variant chosen, Stream
    # K5); the variant address 301s there. The guid stays the variant address: it is an
    # identity, and changing it would announce every item to every reader again.
    guid = product_path(product.id, product.name)
    return FeedItem(title=product.name, path=product.path or guid, published=added, description=description, guid=guid)


def article_item(out: ArticleOut) -> FeedItem:
    """One published article as a feed item: its title and description, never the body."""
    return FeedItem(
        title=out.title,
        path=out.path,
        published=out.published_at or out.updated_at,
        description=out.description,
        guid=out.path,
    )


def feed_items(db: Session) -> list[FeedItem]:
    """Products and articles in one channel, newest first."""
    items = [product_item(p, added) for p, added in catalog_queries.newest_product_variants(db, FEED_ITEMS)]
    items.extend(article_item(a) for a in editorial.feed_items(db, limit=FEED_ARTICLES))
    return sorted(items, key=lambda i: i.published, reverse=True)


def rss_xml(items: list[FeedItem], base: str, built: datetime | None = None) -> str:
    """RSS 2.0 with absolute links; the channel's build date is the newest item."""
    built = built or max((i.published for i in items), default=datetime.now(UTC))
    out = [
        '<?xml version="1.0" encoding="UTF-8"?>',
        '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel>',
        f"<title>{html.escape(FEED_TITLE)}</title>",
        f"<link>{html.escape(base + '/')}</link>",
        f"<description>{html.escape(FEED_DESCRIPTION)}</description>",
        "<language>en</language>",
        f"<lastBuildDate>{format_datetime(built.astimezone(UTC), usegmt=True)}</lastBuildDate>",
        f'<atom:link href="{html.escape(base + "/feed.xml", quote=True)}" rel="self" type="application/rss+xml" />',
    ]
    for item in items:
        link = html.escape(base + item.path)
        out.append(
            "<item>"
            f"<title>{html.escape(item.title)}</title>"
            f"<link>{link}</link>"
            f'<guid isPermaLink="true">{html.escape(base + item.guid)}</guid>'
            f"<pubDate>{format_datetime(item.published.astimezone(UTC), usegmt=True)}</pubDate>"
            f"<description>{html.escape(item.description)}</description>"
            "</item>"
        )
    out.append("</channel></rss>")
    return "".join(out)


def llms_txt(facts: DatasetFacts, base: str) -> str:
    """llms.txt (llmstxt.org): a short markdown briefing with the live figures."""
    airports: list[AirportSummary] = facts.airport_list
    categories: list[CategoryCount] = facts.categories
    lines = [
        f"# {SITE_NAME}",
        "",
        "> Duty-free price comparison for travellers: dated observations of the public list prices at "
        "airport duty-free shops, compared per product across the airports that stock it, with "
        "competition medals from the Professor's award network.",
        "",
        "## How to read a price here",
        "",
        "- A price is an observation with a date, never a quote: duty-free pricing varies by "
        "destination, loyalty tier and traveller. Every price on the site shows when it was seen.",
        "- Each product page lists the latest observed price at every airport that stocks it, in the "
        "shop's currency and in US dollars, cheapest first, with a link to the shop's own listing.",
        "- We store facts only (prices, sizes, barcodes, stock, brands), never retailer copy or imagery.",
        "",
        "## The catalogue today",
        "",
        f"- {facts.product_variants:,} products at {facts.airports} airports, {facts.observations:,} dated price "
        f"observations in {facts.currencies} currencies, {facts.with_barcode:,} products with a barcode, "
        f"{facts.awards:,} competition medals attached; observed {fmt_date(facts.first_observed_at)} to "
        f"{fmt_date(facts.last_observed_at)}.",
        "",
        "## Pages",
        "",
        f"- [Every product]({base}/products): the catalogue with search and category filters.",
        f"- [Airports we price]({base}/airports): coverage and freshness per airport.",
    ]
    for airport in airports:
        lines.append(
            f"- [{airport.name} ({airport.iata})]({base}{airport.path}): {airport.product_variants:,} products, "
            f"checked {fmt_date(airport.last_collected_at)}."
        )
    lines += [
        f"- [Travel-retail exclusives]({base}/exclusives): bottles sold only in airports.",
        f"- [Award-winning bottles]({base}/awards): medal winners on duty-free shelves, with prices.",
        f"- [The dataset]({base}/data): what is collected, how, coverage and contact.",
        "",
        "## Categories",
        "",
    ]
    lines += [
        f"- [{c.category}]({base}{category_path(c.category)}): {c.count:,} products" for c in categories
    ]
    lines += [
        "",
        "## Machine-readable",
        "",
        f"- Sitemap: {base}/sitemap.xml (every product and airport page with its last change).",
        f"- Feed: {base}/feed.xml (products newly added to the catalogue, and the articles).",
        f"- Articles: {base}/articles (the Professor's guides and notes; each page carries schema.org Article).",
        "- ProductVariant pages carry schema.org ProductVariant with an AggregateOffer in USD and one Offer per shop "
        "in the shop's currency; airport pages carry CollectionPage, ItemList and Airport; the dataset "
        f"page carries Dataset. ProductVariant URLs are {base}/products/<slug>-<id>; airport URLs are "
        f"{base}/airports/<iata>-<city>.",
        "- Our own reader is DutyFreeProfessorBot; its identity page is https://bot.dutyfreeprofessor.com.",
        "",
    ]
    return "\n".join(lines)
