"""Stobo MCP Server — exposes Stobo API as tools for Claude Desktop."""

from __future__ import annotations

import json
import os

from mcp.server.fastmcp import FastMCP

from stobo.client import StoboAPIError, StoboClient

mcp = FastMCP(
    "Stobo",
    instructions="""Stobo is an AI-powered SEO and AEO (Answer Engine Optimization) audit platform.

CRITICAL ROUTING RULE — READ THIS FIRST:
- DEFAULT: Always use audit_site for any domain, homepage, or generic "audit this" request.
- ONLY use audit_article when the URL is clearly a specific blog post or article (contains /blog/, /post/, /article/, /news/ in the path, or user explicitly says "audit this article/post").
- When in doubt, use audit_site. It is the main entry point.

ROUTING EXAMPLES:
- "audit phantombuster.com" → audit_site ✅ (NOT audit_article)
- "audit https://example.com" → audit_site ✅
- "analyse my website" → audit_site ✅
- "run an audit on example.com" → audit_site ✅
- "check https://example.com/blog/my-post" → audit_article ✅ (specific article URL)
- "audit this article: URL" → audit_article ✅ (user said "article")

TWO LEVELS:
1. audit_site — Website-level: 30 SEO checks + 7 AEO checks + blog scanner + sitemap discovery. The DEFAULT tool.
2. audit_article — Article-level: 7 SEO + 14 AEO checks on a single blog post/article. Only for specific article URLs.

AUDIT_SITE RESPONSE STRUCTURE:
The response contains these sections — present ALL of them to the user:

1. COMBINED SCORE: combined_percentage (average of SEO % + AEO %). Show this first as the headline metric.

2. SEO AUDIT (seo_audit):
   - grade (A-F), overall_score / max_points (e.g. "245/335")
   - category_scores: 7 categories (Content, Links, Technical, Performance, Security, Social, Accessibility) — show as a table with score/max_points per category
   - checks: 30 individual checks with status (excellent/good/needs_improvement/critical), score, message
   - recommendations: sorted by priority (high → medium → low) — show top 5-10

3. AEO AUDIT (aeo_audit): 7 checks for AI discoverability (all snake_case keys):
   - robots_ai: AI crawler access in robots.txt (25 pts)
   - llms_txt: presence of llms.txt file (25 pts)
   - freshness: content freshness signals — dateModified schema (25 pts)
   - faqs: FAQ content in AI-preferred format (10 pts)
   - faq_schema: FAQ structured data markup (15 pts)
   - direct_answer: concise answers in first 40-60 words (25 pts)
   - sitemap: XML sitemap discoverability (10 pts)
   - Each check has status + score + max_points + message. Show as a checklist with pass/fail.
   - Overall: score / max_points + percentage (135 max points)

4. BLOG DETECTION (blog_detection):
   - has_blog, blog_url, article_count, sitemap_url (all snake_case)
   - If blog found, proactively suggest next steps:
     "I found a blog at {blog_url} with ~{article_count} articles."
     - freshness_audit(sitemap_url) to scan schema compliance
     - audit_article(article_url) to deep-dive specific posts

5. SITEMAP DISCOVERY (sitemap_discovery):
   - total_urls: total pages found in sitemap
   - categories: pages classified into brand categories (brand pages, blog articles, product pages, etc.)
   - blog_article_count: articles found via sitemap
   - Show a summary: "Found {total_urls} pages: {N} blog articles, {M} brand pages, etc."

6. CACHE: cached (bool) — mention if results are from cache ("Results from cache, less than 24h old")

FORMATTING GUIDANCE:
- Lead with combined % as headline: "## {domain} — {combined_percentage}% (SEO + AEO)"
- Show SEO grade prominently: "SEO: {grade} ({overall_score}/{max_points})"
- Show AEO score: "AEO: {percentage}% ({score}/{max_points})"
- Use tables for category breakdown and AEO checklist
- Only show failing/critical checks in detail, don't dump all 30
- Always end with top 3-5 actionable recommendations

LLMS.TXT GENERATOR:
- If audit_site shows llms.txt score is critical or needs_improvement, proactively offer: "Your llms.txt is missing/incomplete. Want me to generate one?"
- Use generate_llms_txt(url) to create a spec-compliant llms.txt file.
- Show the generated content and explain how to deploy it (save as /llms.txt at domain root).

ROBOTS.TXT GENERATOR:
- If audit_site shows robots_ai score is critical or needs_improvement, proactively offer: "Your robots.txt is missing AI crawler rules. Want me to generate an optimized version?"
- Use generate_robots_txt(url) to create a robots.txt with all 21 AI crawlers allowed.
- Show the score improvement (before → after), list of crawlers added, and the full generated content.
- Explain how to deploy it (replace /robots.txt at domain root). Free, no auth required, instant.

SITEMAP.XML GENERATOR:
- If audit_site shows sitemap score is critical or needs_improvement, proactively offer: "Your sitemap.xml is missing or incomplete. Want me to generate one?"
- Use generate_sitemap(url) to crawl the site and produce a proper sitemap.xml.
- Show the URL count, crawl depth, and whether an existing sitemap was found.
- Explain how to deploy it (save as /sitemap.xml at domain root). Free, no auth required.

TOOLS THAT WORK WITHOUT AUTH: audit_site, audit_article, audit_seo, audit_aeo, freshness_audit, generate_robots_txt, generate_sitemap
TOOLS THAT NEED API KEY: generate_llms_txt, extract_tone, get_tone, list_tone_profiles, optimize, get_job, export_report, list_audits, get_audit""",
)


def _get_client() -> StoboClient:
    api_key = os.environ.get("STOBO_API_KEY", "")
    base_url = os.environ.get("STOBO_BASE_URL", "https://api.trystobo.com")
    return StoboClient(base_url=base_url, api_key=api_key, user_agent="stobo-mcp/0.3.1")


def _call(fn, *args, **kwargs) -> str:
    """Call a client method and return JSON string, handling errors."""
    try:
        result = fn(*args, **kwargs)
        return json.dumps(result, indent=2, default=str)
    except StoboAPIError as e:
        return json.dumps({"error": e.detail, "status_code": e.status_code})
    except Exception as e:
        return json.dumps({"error": str(e)})


# ── Site Audit (orchestrator) ────────────────────────────────────────


@mcp.tool()
def audit_site(url: str) -> str:
    """Run a full site audit: 30 SEO checks + 7 AEO checks + blog scanner + sitemap discovery. This is the main entry point for analysing a website. Returns combined percentage, SEO grade with category breakdown, AEO checklist (7 checks), blog detection (URL + article count), and sitemap discovery (pages classified by category). Results are cached for 24h. Present all sections to the user — see formatting guidance in instructions."""
    client = _get_client()
    return _call(client.audit_site, url)


# ── Article Audit Tools ─────────────────────────────────────────────


@mcp.tool()
def audit_article(
    url: str,
    keyword: str | None = None,
    use_playwright: bool = False,
) -> str:
    """Audit a single blog post or article page (NOT a homepage or domain — use audit_site for that). Runs 7 SEO + 14 AEO checks. Only use when the URL points to a specific article (e.g. /blog/my-post)."""
    client = _get_client()
    return _call(client.audit_article, url, keyword=keyword, use_playwright=use_playwright)


@mcp.tool()
def audit_seo(url: str, keyword: str | None = None) -> str:
    """SEO-only audit on a single article page. Use audit_site for full website SEO. Use audit_article for combined SEO+AEO on an article."""
    client = _get_client()
    return _call(client.audit_seo, url, keyword=keyword)


@mcp.tool()
def audit_aeo(url: str) -> str:
    """AEO-only audit on a single article page. Use audit_site for full website AEO. Use audit_article for combined SEO+AEO on an article."""
    client = _get_client()
    return _call(client.audit_aeo, url)


@mcp.tool()
def get_audit(audit_id: str) -> str:
    """Fetch a previously completed audit by its ID."""
    client = _get_client()
    return _call(client.get_audit, audit_id)


@mcp.tool()
def list_audits(skip: int = 0, limit: int = 20) -> str:
    """List recent audits with pagination."""
    client = _get_client()
    return _call(client.list_audits, skip=skip, limit=limit)


# ── llms.txt Generator ──────────────────────────────────────────────


@mcp.tool()
def generate_llms_txt(url: str) -> str:
    """Generate a llms.txt file for a website. Use after audit_site shows a missing or low-scoring llms.txt (critical/needs_improvement). Returns ready-to-deploy markdown content that the user can place at their domain root. Takes ~8-15 seconds."""
    client = _get_client()
    return _call(client.generate_llms_txt, url)


# ── robots.txt Generator ──────────────────────────────────────────


@mcp.tool()
def generate_robots_txt(url: str) -> str:
    """Generate an AI-crawler-optimized robots.txt for a website. Use after audit_site shows a low robots_ai score (critical/needs_improvement). Adds all 21 AI crawlers (GPTBot, ClaudeBot, etc.) while preserving existing rules. Free, no auth, instant. Shows score improvement and full generated content."""
    client = _get_client()
    return _call(client.generate_robots_txt, url)


# ── sitemap.xml Generator ──────────────────────────────────────────


@mcp.tool()
def generate_sitemap(url: str, max_urls: int = 200) -> str:
    """Generate a sitemap.xml by crawling a website's internal pages. Use after audit_site shows a low sitemap score (critical/needs_improvement). Discovers pages via BFS crawl, generates proper XML with lastmod, changefreq, and priority. Free, no auth required."""
    client = _get_client()
    return _call(client.generate_sitemap, url, max_urls=max_urls)


# ── Tone Tools ───────────────────────────────────────────────────────


@mcp.tool()
def extract_tone(
    blog_url: str,
    customer_id: str | None = None,
    max_articles: int = 10,
) -> str:
    """Extract brand voice profile from a blog URL by analyzing its articles."""
    client = _get_client()
    return _call(
        client.extract_tone, blog_url, customer_id=customer_id, max_articles=max_articles
    )


@mcp.tool()
def get_tone(customer_id: str) -> str:
    """Get a stored brand voice/tone profile by customer ID."""
    client = _get_client()
    return _call(client.get_tone, customer_id)


@mcp.tool()
def list_tone_profiles() -> str:
    """List all stored brand voice profiles."""
    client = _get_client()
    return _call(client.list_tone_profiles)


@mcp.tool()
def delete_tone(customer_id: str) -> str:
    """Delete a stored brand voice profile by customer ID."""
    client = _get_client()
    return _call(client.delete_tone, customer_id)


# ── Optimize Tools ───────────────────────────────────────────────────


@mcp.tool()
def optimize(
    url: str,
    customer_id: str | None = None,
    sync: bool = False,
) -> str:
    """Start a full optimization pipeline: audit → tone → rewrite. Returns a job ID to track progress."""
    client = _get_client()
    return _call(client.optimize, url, customer_id=customer_id, sync=sync)


@mcp.tool()
def get_job(job_id: str) -> str:
    """Check the status of an optimization job. Returns progress, status, and results when complete."""
    client = _get_client()
    return _call(client.get_job, job_id)


@mcp.tool()
def get_job_preview(job_id: str) -> str:
    """Get before/after preview of a completed optimization job. Shows original vs rewritten content."""
    client = _get_client()
    return _call(client.get_job_preview, job_id)


@mcp.tool()
def list_jobs(status: str | None = None, limit: int = 50) -> str:
    """List optimization jobs with optional status filter (pending/running/completed/failed)."""
    client = _get_client()
    return _call(client.list_jobs, status=status, limit=limit)


# ── Freshness Tools ──────────────────────────────────────────────────


@mcp.tool()
def freshness_audit(sitemap_url: str, limit: int = 50) -> str:
    """Audit a sitemap for dateModified schema compliance. Checks content freshness signals."""
    client = _get_client()
    return _call(client.freshness_audit, sitemap_url, limit=limit)


@mcp.tool()
def get_freshness(audit_id: str) -> str:
    """Get a previously completed freshness audit by its ID."""
    client = _get_client()
    return _call(client.get_freshness, audit_id)


# ── Export Tools ─────────────────────────────────────────────────────


@mcp.tool()
def export_report(
    customer_id: str,
    data_type: str,
) -> str:
    """Generate a markdown export report. data_type can be 'seo', 'aeo', 'tone', or 'full'."""
    client = _get_client()
    return _call(client.export, customer_id, data_type)


@mcp.tool()
def get_export(customer_id: str, data_type: str) -> str:
    """Get a cached export report by customer ID and type (brand_voice/seo_audit/article_rewrite)."""
    client = _get_client()
    return _call(client.get_export, customer_id, data_type)


# ── Credit Tools ─────────────────────────────────────────────────────


@mcp.tool()
def get_credits() -> str:
    """Get credit usage for the current API key. Shows used/total/remaining, plan name, reset date, and breakdown by action."""
    client = _get_client()
    return _call(client.get_credits)


def main():
    mcp.run()


if __name__ == "__main__":
    main()
