#!/usr/bin/env python3
"""
VPS-wide guides.rickpina.cloud Patreon CTA injector + free-browse cleanup.
Idempotent. Skips /opt/guides/ripm/.
"""
from __future__ import annotations

import re
import shutil
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path("/opt/guides")
BACKUP_DIR = Path("/opt/guides/_backups/patreon-cta-" + datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S"))

# Series paths that may stay linked to each other (VCMI It's Bigger Than You parts)
VCMI_SERIES_PATHS = {
    "/vcmi/its-bigger-than-you/",
    "/vcmi/its-bigger-than-you-bible-study/",
    "/vcmi/living-legacy-minded-part-2/",
    "/vcmi/god-builds-families/",
}

JPM_SERIES_RE = re.compile(
    r"https?://guides\.rickpina\.cloud/jpm-9-books/|/jpm-9-books/",
    re.I,
)

THEME_COLORS = {
    "crimson": {
        "band": "#121212",
        "accent": "#C41E3A",
        "accent_soft": "rgba(196,30,58,0.18)",
        "card_bg": "#1A1A1A",
        "card_border": "rgba(196,30,58,0.35)",
        "muted": "#B8B8B8",
        "btn_bg": "#C41E3A",
        "btn_hover": "#a81932",
        "eyebrow": "#C41E3A",
    },
    "purple": {
        "band": "#232060",
        "accent": "#a5a2e0",
        "accent_soft": "rgba(163,160,220,0.18)",
        "card_bg": "#2a2870",
        "card_border": "rgba(163,160,220,0.35)",
        "muted": "#d5d3ee",
        "btn_bg": "#4a47a3",
        "btn_hover": "#333086",
        "eyebrow": "#d5d3ee",
    },
    "navy": {
        "band": "#0A2540",
        "accent": "#C4A35A",
        "accent_soft": "rgba(196,163,90,0.18)",
        "card_bg": "#0D2E4C",
        "card_border": "rgba(196,163,90,0.35)",
        "muted": "#B8B8B8",
        "btn_bg": "#C4A35A",
        "btn_hover": "#a8873f",
        "eyebrow": "#C4A35A",
        "btn_text": "#0A2540",
    },
}


def is_ripm(p: Path) -> bool:
    parts = p.resolve().parts
    return "ripm" in parts


def detect_theme(path: Path, html: str) -> str:
    rel = str(path.relative_to(ROOT)).replace("\\", "/")
    if rel.startswith("vcmi/") or "/vcmi/" in rel:
        return "purple"
    if rel.startswith("jpm-9-books/") or "/jpm-9-books/" in rel:
        return "navy"
    low = html.lower()
    if "#c41e3a" in low or "--crimson" in low:
        return "crimson"
    if "#333086" in low or "#232060" in low or "--purple" in low:
        return "purple"
    if "#0a2540" in low or "#c4a35a" in low:
        return "navy"
    # Infer from footer / dark bg vars
    if re.search(r"--color-bg-dark:\s*#121212", html, re.I):
        return "crimson"
    if re.search(r"--color-bg-dark:\s*#0A2540", html, re.I):
        return "navy"
    if re.search(r"--dark:\s*#121212", html, re.I):
        return "crimson"
    return "crimson"  # default Inspired BWR / premium guides


def build_cta(theme: str) -> str:
    c = THEME_COLORS[theme]
    btn_text = c.get("btn_text", "#ffffff")
    return f'''
<section class="patreon-join" id="join-patreon" aria-label="Join Rick and Isabella Pina Coaching on Patreon">
  <style>
    .patreon-join {{
      background: {c["band"]};
      color: #fff;
      padding: 64px 24px 72px;
      margin: 0;
      box-sizing: border-box;
      font-family: Montserrat, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
    }}
    .patreon-join *, .patreon-join *::before, .patreon-join *::after {{ box-sizing: border-box; }}
    .patreon-join__inner {{
      max-width: 1080px;
      margin: 0 auto;
      text-align: center;
    }}
    .patreon-join__eyebrow {{
      font-size: 12px;
      font-weight: 800;
      letter-spacing: 0.22em;
      text-transform: uppercase;
      color: {c["eyebrow"]};
      margin: 0 0 14px;
    }}
    .patreon-join__headline {{
      font-size: clamp(26px, 4vw, 40px);
      font-weight: 800;
      line-height: 1.15;
      margin: 0 0 18px;
      color: #fff;
    }}
    .patreon-join__body {{
      font-size: 16px;
      line-height: 1.65;
      color: {c["muted"]};
      max-width: 760px;
      margin: 0 auto 36px;
    }}
    .patreon-join__tiers {{
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 18px;
      text-align: left;
      margin: 0 0 36px;
    }}
    .patreon-join__tier {{
      background: {c["card_bg"]};
      border: 1px solid {c["card_border"]};
      border-radius: 14px;
      padding: 22px 20px 24px;
      min-height: 100%;
    }}
    .patreon-join__tier-name {{
      font-size: 13px;
      font-weight: 800;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: {c["accent"]};
      margin: 0 0 8px;
    }}
    .patreon-join__tier-price {{
      font-size: 22px;
      font-weight: 800;
      color: #fff;
      margin: 0 0 12px;
    }}
    .patreon-join__tier-desc {{
      font-size: 14px;
      line-height: 1.55;
      color: {c["muted"]};
      margin: 0;
    }}
    .patreon-join__btn {{
      display: inline-block;
      background: {c["btn_bg"]};
      color: {btn_text} !important;
      text-decoration: none !important;
      font-weight: 800;
      font-size: 15px;
      letter-spacing: 0.04em;
      padding: 16px 28px;
      border-radius: 999px;
      border: none;
      transition: background 0.2s ease, transform 0.2s ease;
    }}
    .patreon-join__btn:hover {{
      background: {c["btn_hover"]};
      transform: translateY(-1px);
      color: {btn_text} !important;
    }}
    .patreon-join__sub {{
      margin-top: 18px;
      font-size: 13px;
      color: {c["muted"]};
    }}
    .patreon-join__sub a {{
      color: {c["accent"]} !important;
      text-decoration: underline;
      font-weight: 700;
    }}
    @media (max-width: 900px) {{
      .patreon-join__tiers {{ grid-template-columns: 1fr; }}
      .patreon-join {{ padding: 48px 18px 56px; }}
    }}
    @media print {{
      .patreon-join {{ display: none !important; }}
    }}
  </style>
  <div class="patreon-join__inner">
    <p class="patreon-join__eyebrow">COACHING &amp; MENTORSHIP</p>
    <h2 class="patreon-join__headline">Get coached by Rick &amp; Isabella Piña.</h2>
    <p class="patreon-join__body">If you are not a member of our Patreon, join today. Coaching and mentorship designed to maximize your potential, increase your personal productivity, and fulfill your life's purpose. Weekly teaching, a private community, and one-on-one coaching calls, all on Patreon.</p>
    <div class="patreon-join__tiers">
      <div class="patreon-join__tier">
        <div class="patreon-join__tier-name">LIBRARY TIER</div>
        <div class="patreon-join__tier-price">$25/month</div>
        <p class="patreon-join__tier-desc">Hundreds of videos and weekly teaching on life, faith, family, business, and relationships. Private Grace Life Discord, early access, and bonus episodes.</p>
      </div>
      <div class="patreon-join__tier">
        <div class="patreon-join__tier-name">MONTHLY 1:1 MEETINGS</div>
        <div class="patreon-join__tier-price">$100/month</div>
        <p class="patreon-join__tier-desc">Everything in Library, plus a monthly one-on-one phone call with Rick or Isabella (coaching, prayer, personal or business guidance) and direct Q&amp;A access.</p>
      </div>
      <div class="patreon-join__tier">
        <div class="patreon-join__tier-name">DIRECT COACHING</div>
        <div class="patreon-join__tier-price">$500/month</div>
        <p class="patreon-join__tier-desc">Monthly one-on-one video coaching sessions with Rick or Isabella, direct Q&amp;A access, and the full content library.</p>
      </div>
    </div>
    <a class="patreon-join__btn" href="https://www.patreon.com/rickpina" target="_blank" rel="noopener">Join on Patreon →</a>
    <p class="patreon-join__sub">Rick &amp; Isabella Piña Coaching on Patreon · <a href="https://www.patreon.com/rickpina" target="_blank" rel="noopener">patreon.com/rickpina</a></p>
  </div>
</section>
'''.strip() + "\n"


def remove_existing_cta(html: str) -> str:
    # Remove prior injected block (section with style inside)
    return re.sub(
        r"(?is)\s*<section class=\"patreon-join\"[^>]*>.*?</section>\s*",
        "\n",
        html,
        count=1,
    )


def inject_cta(html: str, cta: str) -> str:
    html = remove_existing_cta(html)
    # Prefer immediately before <footer...>, else before </body>
    m = re.search(r"(?i)<footer\b", html)
    if m:
        return html[: m.start()] + "\n" + cta + "\n" + html[m.start() :]
    m = re.search(r"(?i)</body\s*>", html)
    if m:
        return html[: m.start()] + "\n" + cta + "\n" + html[m.start() :]
    return html + "\n" + cta


def strip_bare_guides_footer(html: str) -> str:
    # Bare <p>guides.rickpina.cloud</p>
    html = re.sub(
        r"(?im)^\s*<p[^>]*>\s*guides\.rickpina\.cloud\s*</p>\s*\n?",
        "",
        html,
    )
    # footer__url spanning guides.rickpina.cloud...
    html = re.sub(
        r'(?is)<span class="footer__url">\s*guides\.rickpina\.cloud[^<]*</span>',
        '<span class="footer__url">patreon.com/rickpina</span>',
        html,
    )
    return html


def is_same_page_asset(href: str, page: Path) -> bool:
    """True if href is PDF/slides/download for this page (keep)."""
    h = href.strip()
    if h.startswith("#"):
        return True
    if re.search(r"\.(pdf|png|jpg|jpeg|webp|mp3|mp4|pptx|zip)(\?|$)", h, re.I):
        return True
    return False


def is_vcmi_series_link(href: str) -> bool:
    for path in VCMI_SERIES_PATHS:
        if path in href:
            return True
    return False


def is_jpm_series_link(href: str) -> bool:
    return bool(JPM_SERIES_RE.search(href))


def clean_related_links_ul(html: str) -> str:
    """Remove related-links ULs that farm other guide HTML pages; keep youtube/external."""

    def repl(m: re.Match) -> str:
        block = m.group(0)
        keep_lis = []
        for lim in re.finditer(r"(?is)<li\b[^>]*>.*?</li>", block):
            li = lim.group(0)
            hrefs = re.findall(r'href="([^"]+)"', li)
            if not hrefs:
                keep_lis.append(li)
                continue
            href = hrefs[0]
            # Drop links to other guides HTML on this host
            if re.search(r"guides\.rickpina\.cloud/[^\"#]+\.html", href, re.I):
                continue
            if re.search(r"guides\.rickpina\.cloud/[^\"#]+/?$", href, re.I) and not re.search(
                r"\.(pdf|png|jpg|jpeg|webp|mp3|mp4)(\?|$)", href, re.I
            ):
                # directory-style guide pages
                if "/jpm-9-books/" in href:
                    # series ok if somehow in related-links — still remove from farm
                    continue
                continue
            keep_lis.append(li)
        if not keep_lis:
            return ""
        # rebuild ul
        open_tag = re.match(r"(?is)<ul\b[^>]*>", block).group(0)
        return open_tag + "\n      " + "\n      ".join(keep_lis) + "\n    </ul>"

    return re.sub(r'(?is)<ul class="related-links">\s*.*?</ul>', repl, html)


def rename_related_guides_headings(html: str) -> str:
    html = re.sub(
        r"PDF AND\s*<span([^>]*)>RELATED GUIDES</span>",
        r"PDF AND <span\1>ASSETS</span>",
        html,
        flags=re.I,
    )
    html = re.sub(
        r"(?i)>PDF and Related Guides<",
        ">PDF and Assets<",
        html,
    )
    # Soften asset-tile "Related Guides" label that only points at #assets
    html = re.sub(
        r'(?is)(<a class="asset-tile"[^>]*href="#assets"[^>]*>.*?<span class="asset-tile__title">)Related Guides(</span>)',
        r"\1Guide Assets\2",
        html,
    )
    return html


def clean_vcmi_footer_hub_links(html: str, page: Path) -> str:
    """In VCMI footers, remove free-browse hub / other-session farms; keep series Part links + vcmi.org."""
    if "vcmi" not in page.parts:
        return html

    def footer_repl(m: re.Match) -> str:
        foot = m.group(0)

        def link_repl(lm: re.Match) -> str:
            full = lm.group(0)
            href = lm.group(1)
            text = re.sub(r"<[^>]+>", "", lm.group(2))
            # Always keep non-guides links
            if "guides.rickpina.cloud" not in href and not href.startswith("/vcmi/"):
                return full
            # Keep VCMI series part links
            if is_vcmi_series_link(href):
                # Only keep if clearly series (Part / Bigger / Legacy) OR from a series page
                text_l = text.lower()
                if (
                    "part" in text_l
                    or "bigger" in text_l
                    or "legacy" in text_l
                    or "god builds" in text_l
                    or any(s.strip("/") in str(page) for s in VCMI_SERIES_PATHS)
                ):
                    return full
                # If link is series path but label is something else from series page, keep
                return full
            # Remove hub and other session free-browse
            return ""

        # Replace <a>...</a> then clean leftover separators
        foot2 = re.sub(
            r'(?is)<a\s+[^>]*href="([^"]+)"[^>]*>(.*?)</a>',
            link_repl,
            foot,
        )
        # Clean empty separators like " ·  · " and dangling middots
        foot2 = re.sub(r"(?:\s*&nbsp;\s*&middot;\s*&nbsp;\s*){2,}", " &nbsp;&middot;&nbsp; ", foot2)
        foot2 = re.sub(r"(?:&nbsp;\s*&middot;\s*&nbsp;\s*){2,}", " &nbsp;&middot;&nbsp; ", foot2)
        # Trim leading/trailing middots inside <p>
        def clean_p(pm: re.Match) -> str:
            inner = pm.group(1)
            inner = re.sub(r"^(\s|&nbsp;|&middot;|·|\s)+", "", inner)
            inner = re.sub(r"(\s|&nbsp;|&middot;|·|\s)+$", "", inner)
            inner = re.sub(r"(\s*&nbsp;\s*&middot;\s*&nbsp;\s*)+", " &nbsp;&middot;&nbsp; ", inner)
            if not re.sub(r"\s|&nbsp;|&middot;|·", "", inner):
                return ""
            return f"<p>{inner}</p>"

        foot2 = re.sub(r"(?is)<p>(.*?)</p>", clean_p, foot2)
        # Remove empty paragraphs left behind
        foot2 = re.sub(r"(?is)<p>\s*</p>\s*", "", foot2)
        return foot2

    return re.sub(r"(?is)<footer\b[^>]*>.*?</footer>", footer_repl, html, count=1)


def remove_generic_more_guides_browse(html: str) -> str:
    """Remove hub-style browse-all that points at guides root without Patreon (rare)."""
    # Do not remove Patreon CTAs that say "More Guides on Patreon"
    return html


def process_file(path: Path) -> dict:
    original = path.read_text(encoding="utf-8", errors="replace")
    html = original
    theme = detect_theme(path, html)
    changes = []

    before = html
    html = strip_bare_guides_footer(html)
    if html != before:
        changes.append("bare-footer")

    before = html
    html = clean_related_links_ul(html)
    if html != before:
        changes.append("related-links")

    before = html
    html = rename_related_guides_headings(html)
    if html != before:
        changes.append("rename-related")

    before = html
    html = clean_vcmi_footer_hub_links(html, path)
    if html != before:
        changes.append("vcmi-hub")

    cta = build_cta(theme)
    # Always replace/inject CTA
    had = 'class="patreon-join"' in original or "class='patreon-join'" in original
    html = inject_cta(html, cta)
    changes.append("cta-update" if had else "cta-add")

    if html == original:
        return {"path": str(path), "changed": False, "theme": theme, "changes": []}

    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    rel = path.relative_to(ROOT)
    bak = BACKUP_DIR / rel
    bak.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(path, bak)
    path.write_text(html, encoding="utf-8")
    return {"path": str(path), "changed": True, "theme": theme, "changes": changes}


def main():
    files = sorted(p for p in ROOT.rglob("*.html") if not is_ripm(p))
    # Skip backup dir itself if any
    files = [p for p in files if "/_backups/" not in str(p)]
    skipped_ripm = sorted(p for p in ROOT.rglob("*.html") if is_ripm(p))

    updated = []
    unchanged = []
    failed = []
    theme_counts = {}

    for p in files:
        try:
            # Skip tiny stubs
            if p.stat().st_size < 50:
                unchanged.append(str(p) + " (tiny stub)")
                continue
            res = process_file(p)
            theme_counts[res["theme"]] = theme_counts.get(res["theme"], 0) + 1
            if res["changed"]:
                updated.append(res)
            else:
                unchanged.append(str(p))
        except Exception as e:
            failed.append({"path": str(p), "error": str(e)})

    print("BACKUP", BACKUP_DIR)
    print("UPDATED", len(updated))
    print("UNCHANGED", len(unchanged))
    print("FAILED", len(failed))
    print("SKIPPED_RIPM", len(skipped_ripm))
    print("THEMES", theme_counts)
    print("\n-- updated list --")
    for r in updated:
        print(f"{r['theme']:7} {','.join(r['changes']):30} {r['path']}")
    print("\n-- skipped ripm --")
    for p in skipped_ripm:
        print(p)
    print("\n-- failed --")
    for f in failed:
        print(f)
    print("\n-- unchanged --")
    for u in unchanged:
        print(u)


if __name__ == "__main__":
    main()
