"""
CMS / pSEO admin: article management, template CRUD, programmatic generation.
"""
import json
from datetime import datetime
from functools import wraps
from pathlib import Path

from quart import Blueprint, flash, g, redirect, render_template, request, url_for

from ..core import csrf_protect, execute, fetch_all, fetch_one

bp = Blueprint(
    "cms",
    __name__,
    template_folder=str(Path(__file__).parent / "templates"),
    url_prefix="/admin/cms",
)


def admin_required(f):
    @wraps(f)
    async def decorated(*args, **kwargs):
        if "admin" not in (g.get("user") or {}).get("roles", []):
            await flash("Admin access required.", "error")
            from quart import redirect as _redirect, url_for as _url_for
            return _redirect(_url_for("auth.login"))
        return await f(*args, **kwargs)
    return decorated


# =============================================================================
# Article helpers
# =============================================================================

async def list_articles(limit: int = 200) -> list[dict]:
    return await fetch_all(
        """SELECT a.*, at.name AS template_name
           FROM articles a
           LEFT JOIN template_data td ON td.id = a.template_data_id
           LEFT JOIN article_templates at ON at.id = td.template_id
           ORDER BY a.created_at DESC LIMIT ?""",
        (limit,),
    )


async def get_article(article_id: int) -> dict | None:
    return await fetch_one("SELECT * FROM articles WHERE id = ?", (article_id,))


async def list_article_templates() -> list[dict]:
    return await fetch_all(
        """SELECT at.*, COUNT(td.id) AS data_count,
                  COUNT(td.article_id) AS generated_count
           FROM article_templates at
           LEFT JOIN template_data td ON td.template_id = at.id
           GROUP BY at.id ORDER BY at.created_at DESC"""
    )


async def get_article_template(template_id: int) -> dict | None:
    return await fetch_one("SELECT * FROM article_templates WHERE id = ?", (template_id,))


async def list_template_data(template_id: int) -> list[dict]:
    return await fetch_all(
        "SELECT * FROM template_data WHERE template_id = ? ORDER BY id",
        (template_id,),
    )


async def generate_article_from_data(data_row: dict, tmpl: dict) -> int | None:
    """Generate (or regenerate) a single article from a template_data row."""
    from jinja2 import BaseLoader, Environment

    try:
        data = json.loads(data_row["data_json"])
    except (json.JSONDecodeError, KeyError):
        return None

    env = Environment(loader=BaseLoader())

    def _render(pattern: str) -> str:
        try:
            return env.from_string(pattern).render(**data)
        except Exception:
            return pattern

    url_path = "/" + _render(tmpl["url_pattern"]).strip("/")
    slug = url_path.strip("/").replace("/", "-")
    title = _render(tmpl["title_pattern"])
    meta_description = _render(tmpl.get("meta_description_pattern") or "")
    now = datetime.utcnow().isoformat()

    existing = await fetch_one("SELECT id FROM articles WHERE url_path = ?", (url_path,))
    if existing:
        article_id = existing["id"]
        await execute(
            """UPDATE articles
               SET title = ?, meta_description = ?, status = 'published',
                   published_at = COALESCE(published_at, ?),
                   template_data_id = ?, updated_at = ?
               WHERE id = ?""",
            (title, meta_description, now, data_row["id"], now, article_id),
        )
    else:
        article_id = await execute(
            """INSERT INTO articles
               (url_path, slug, title, meta_description, status, published_at,
                template_data_id, created_at)
               VALUES (?, ?, ?, ?, 'published', ?, ?, ?)""",
            (url_path, slug, title, meta_description, now, data_row["id"], now),
        )

    await execute(
        "UPDATE template_data SET article_id = ? WHERE id = ?",
        (article_id, data_row["id"]),
    )
    return article_id


# =============================================================================
# Article routes
# =============================================================================

@bp.route("/")
@admin_required
async def cms_index():
    articles = await list_articles()
    return await render_template(
        "admin/cms.html",
        articles=articles,
        published_count=sum(1 for a in articles if a["status"] == "published"),
        draft_count=sum(1 for a in articles if a["status"] == "draft"),
    )


@bp.route("/new", methods=["GET", "POST"])
@admin_required
@csrf_protect
async def cms_new():
    if request.method == "POST":
        form = await request.form
        url_path = ("/" + form.get("url_path", "").strip("/")).rstrip("/") or "/"
        slug = url_path.strip("/").replace("/", "-") or "untitled"
        title = (form.get("title") or "").strip()
        meta_description = (form.get("meta_description") or "").strip()
        status = form.get("status", "draft")
        now = datetime.utcnow().isoformat()
        published_at = now if status == "published" else None

        article_id = await execute(
            """INSERT INTO articles (url_path, slug, title, meta_description, status, published_at, created_at)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            (url_path, slug, title, meta_description, status, published_at, now),
        )
        await flash(f"Article '{title}' created.", "success")
        return redirect(url_for("cms.cms_edit", article_id=article_id))

    return await render_template("admin/cms_editor.html", article=None)


@bp.route("/<int:article_id>/edit", methods=["GET", "POST"])
@admin_required
@csrf_protect
async def cms_edit(article_id: int):
    article = await get_article(article_id)
    if not article:
        await flash("Article not found.", "error")
        return redirect(url_for("cms.cms_index"))

    if request.method == "POST":
        form = await request.form
        url_path = ("/" + form.get("url_path", "").strip("/")).rstrip("/") or "/"
        slug = url_path.strip("/").replace("/", "-") or f"article-{article_id}"
        title = (form.get("title") or "").strip()
        meta_description = (form.get("meta_description") or "").strip()
        og_image_url = (form.get("og_image_url") or "").strip()
        status = form.get("status", "draft")
        now = datetime.utcnow().isoformat()
        published_at = article["published_at"] or (now if status == "published" else None)

        await execute(
            """UPDATE articles
               SET url_path = ?, slug = ?, title = ?, meta_description = ?,
                   og_image_url = ?, status = ?, published_at = ?, updated_at = ?
               WHERE id = ?""",
            (url_path, slug, title, meta_description, og_image_url or None,
             status, published_at, now, article_id),
        )
        await flash("Article saved.", "success")
        return redirect(url_for("cms.cms_edit", article_id=article_id))

    article = await get_article(article_id)
    return await render_template("admin/cms_editor.html", article=article)


@bp.route("/<int:article_id>/publish", methods=["POST"])
@admin_required
@csrf_protect
async def cms_publish(article_id: int):
    now = datetime.utcnow().isoformat()
    await execute(
        "UPDATE articles SET status = 'published', published_at = COALESCE(published_at, ?), updated_at = ? WHERE id = ?",
        (now, now, article_id),
    )
    await flash("Article published.", "success")
    return redirect(url_for("cms.cms_index"))


@bp.route("/<int:article_id>/unpublish", methods=["POST"])
@admin_required
@csrf_protect
async def cms_unpublish(article_id: int):
    await execute(
        "UPDATE articles SET status = 'draft', updated_at = ? WHERE id = ?",
        (datetime.utcnow().isoformat(), article_id),
    )
    await flash("Article moved to draft.", "success")
    return redirect(url_for("cms.cms_index"))


@bp.route("/<int:article_id>/delete", methods=["POST"])
@admin_required
@csrf_protect
async def cms_delete(article_id: int):
    await execute("DELETE FROM articles WHERE id = ?", (article_id,))
    await flash("Article deleted.", "success")
    return redirect(url_for("cms.cms_index"))


# =============================================================================
# Article template routes
# =============================================================================

@bp.route("/templates")
@admin_required
async def template_list():
    templates = await list_article_templates()
    return await render_template("admin/cms_templates.html", templates=templates)


@bp.route("/templates/new", methods=["GET", "POST"])
@admin_required
@csrf_protect
async def template_new():
    if request.method == "POST":
        form = await request.form
        name = (form.get("name") or "").strip()
        slug = (form.get("slug") or name.lower().replace(" ", "-")).strip()
        url_pattern = (form.get("url_pattern") or "").strip()
        title_pattern = (form.get("title_pattern") or "").strip()
        meta_description_pattern = (form.get("meta_description_pattern") or "").strip()
        body_template = (form.get("body_template") or "").strip()
        now = datetime.utcnow().isoformat()

        template_id = await execute(
            """INSERT INTO article_templates
               (name, slug, url_pattern, title_pattern, meta_description_pattern, body_template, created_at)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            (name, slug, url_pattern, title_pattern, meta_description_pattern or None,
             body_template, now),
        )
        await flash(f"Template '{name}' created.", "success")
        return redirect(url_for("cms.template_data_view", template_id=template_id))

    return await render_template("admin/cms_template_editor.html", template=None)


@bp.route("/templates/<int:template_id>/edit", methods=["GET", "POST"])
@admin_required
@csrf_protect
async def template_edit(template_id: int):
    tmpl = await get_article_template(template_id)
    if not tmpl:
        await flash("Template not found.", "error")
        return redirect(url_for("cms.template_list"))

    if request.method == "POST":
        form = await request.form
        now = datetime.utcnow().isoformat()
        await execute(
            """UPDATE article_templates
               SET name = ?, url_pattern = ?, title_pattern = ?,
                   meta_description_pattern = ?, body_template = ?, updated_at = ?
               WHERE id = ?""",
            (
                (form.get("name") or tmpl["name"]).strip(),
                (form.get("url_pattern") or "").strip(),
                (form.get("title_pattern") or "").strip(),
                (form.get("meta_description_pattern") or "").strip() or None,
                (form.get("body_template") or "").strip(),
                now,
                template_id,
            ),
        )
        await flash("Template saved.", "success")
        return redirect(url_for("cms.template_data_view", template_id=template_id))

    return await render_template("admin/cms_template_editor.html", template=tmpl)


@bp.route("/templates/<int:template_id>")
@admin_required
async def template_data_view(template_id: int):
    tmpl = await get_article_template(template_id)
    if not tmpl:
        await flash("Template not found.", "error")
        return redirect(url_for("cms.template_list"))
    data_rows = await list_template_data(template_id)
    return await render_template(
        "admin/cms_template_data.html",
        template=tmpl,
        data_rows=data_rows,
    )


@bp.route("/templates/<int:template_id>/data/add", methods=["POST"])
@admin_required
@csrf_protect
async def template_data_add(template_id: int):
    form = await request.form
    data_json = (form.get("data_json") or "{}").strip()
    try:
        json.loads(data_json)
    except json.JSONDecodeError:
        await flash("Invalid JSON.", "error")
        return redirect(url_for("cms.template_data_view", template_id=template_id))

    await execute(
        "INSERT INTO template_data (template_id, data_json, created_at) VALUES (?, ?, ?)",
        (template_id, data_json, datetime.utcnow().isoformat()),
    )
    await flash("Data row added.", "success")
    return redirect(url_for("cms.template_data_view", template_id=template_id))


@bp.route("/templates/<int:template_id>/generate", methods=["POST"])
@admin_required
@csrf_protect
async def template_bulk_generate(template_id: int):
    tmpl = await get_article_template(template_id)
    if not tmpl:
        await flash("Template not found.", "error")
        return redirect(url_for("cms.template_list"))

    pending = await fetch_all(
        "SELECT * FROM template_data WHERE template_id = ? AND article_id IS NULL",
        (template_id,),
    )
    generated = sum(1 for row in pending if await generate_article_from_data(row, tmpl))
    await flash(f"Generated {generated} article(s) from {len(pending)} pending row(s).", "success")
    return redirect(url_for("cms.template_data_view", template_id=template_id))


@bp.route("/templates/<int:template_id>/regenerate", methods=["POST"])
@admin_required
@csrf_protect
async def template_regenerate_all(template_id: int):
    tmpl = await get_article_template(template_id)
    if not tmpl:
        await flash("Template not found.", "error")
        return redirect(url_for("cms.template_list"))

    all_rows = await list_template_data(template_id)
    regenerated = sum(1 for row in all_rows if await generate_article_from_data(row, tmpl))
    await flash(f"Regenerated {regenerated} article(s).", "success")
    return redirect(url_for("cms.template_data_view", template_id=template_id))
