# ══════════════════════════════════════════════════════════
#  engines/utils.py
#  Shared: prompt loader, crawler, parsers, message builder
# ══════════════════════════════════════════════════════════

import logging
import os
import re
import requests
from typing import Optional
from bs4 import BeautifulSoup

from config import META_COUNT

logger = logging.getLogger(__name__)

_prompt_cache: dict = {}


# ── Prompt loader ─────────────────────────────────────────
def load_prompt_file(filename: str) -> str:
    if filename not in _prompt_cache:
        if not os.path.exists(filename):
            raise FileNotFoundError(f"File prompt tidak ditemukan: {filename}")
        with open(filename, "r", encoding="utf-8") as f:
            _prompt_cache[filename] = f.read()
        logger.info(f"Prompt cached: {filename}")
    return _prompt_cache[filename]


# ── Unified AI caller ─────────────────────────────────────
async def call_ai(user_message: str, system_prompt: str,
                  message, task: str, ai_provider: str) -> Optional[str]:
    """
    Route ke Claude atau Gemini berdasarkan ai_provider.
    Claude: system_prompt + user_message (cached)
    Gemini: system_prompt + user_message digabung jadi 1 prompt
    """
    if ai_provider == "gemini":
        from engines.gemini_client import call_gemini
        combined = f"{system_prompt}\n\n{user_message}"
        return await call_gemini(combined, message, task)
    else:
        from engines.claude_client import call_claude
        return await call_claude(user_message, system_prompt, message, task)


# ── Crawler ──────────────────────────────────────────────
def crawl_url(url: str) -> str:
    try:
        headers  = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
        response = requests.get(url, headers=headers, timeout=8)
        if response.status_code == 200:
            soup      = BeautifulSoup(response.text, 'html.parser')
            title     = soup.title.string if soup.title else ""
            meta_desc = soup.find('meta', attrs={'name': 'description'})
            meta_desc = meta_desc.get('content', '') if meta_desc else ""
            return f"HTML Title: {title} | Meta Desc: {meta_desc}"
    except Exception as e:
        logger.error(f"Crawl failed [{url}]: {e}")
    return "Tidak bisa mengakses URL."


# ── Message builders ─────────────────────────────────────
def build_title_user_message(brand: str, url: str, keyword: str,
                              web_context: str, phase: str, tone: str,
                              title_count: int, mode_instruction: str = "",
                              revision_note: str = None) -> str:
    msg = (
        f"Gunakan nilai berikut untuk menggantikan semua placeholder:\n\n"
        f"{{{{BRAND}}}}       → {brand}\n"
        f"{{{{URL}}}}         → {url}\n"
        f"{{{{KEYWORD}}}}     → {keyword}\n"
        f"{{{{WEB_CONTEXT}}}} → {web_context}\n"
        f"{{{{PHASE}}}}       → {phase}\n"
        f"{{{{TONE}}}}        → {tone}\n"
        f"{{{{COUNT}}}}       → {title_count}  (hasilkan {title_count} title saja)"
    )
    if mode_instruction:
        msg += mode_instruction
    if revision_note:
        msg += f"\n\n==== REVISION NOTES ====\nRevisi dengan instruksi: '{revision_note}'"
    return msg


def build_meta_user_message(brand: str, url: str, title: str,
                             web_context: str, phase: str, tone: str) -> str:
    return (
        f"Gunakan nilai berikut untuk menggantikan semua placeholder:\n\n"
        f"{{{{BRAND}}}}       → {brand}\n"
        f"{{{{URL}}}}         → {url}\n"
        f"{{{{TITLE}}}}       → {title}\n"
        f"{{{{WEB_CONTEXT}}}} → {web_context}\n"
        f"{{{{PHASE}}}}       → {phase}\n"
        f"{{{{TONE}}}}        → {tone}\n"
        f"{{{{COUNT}}}}       → {META_COUNT}  (hasilkan {META_COUNT} meta desc saja)"
    )


def build_article_user_message(brand: str, title: str, meta_desc: str) -> str:
    return (
        f"Gunakan nilai berikut untuk menggantikan semua placeholder:\n\n"
        f"{{{{BRAND}}}}     → {brand}\n"
        f"{{{{TITLE}}}}     → {title}\n"
        f"{{{{META_DESC}}}} → {meta_desc}"
    )


# ── Parsers ──────────────────────────────────────────────
def parse_titles(response_text: str, limit: int = 3):
    titles, panel = [], ""
    for line in response_text.split('\n'):
        line = line.strip()
        if re.match(r'^([1-9])\.\s', line):
            titles.append(re.sub(r'^([1-9])\.\s*', '', line).strip())
        elif any(k in line for k in ["SpamBrain", "E-E-A-T", "Verdict", "JUDGE PANEL", "⚖️"]):
            panel += f"{line}\n"
    if not titles:
        titles = [t.strip() for t in response_text.split('\n')
                  if t.strip() and len(t.strip()) < 120][:limit]
    return titles[:limit], panel.strip()


def parse_meta_descs(response_text: str, limit: int = 2):
    results, current_meta, current_char = [], None, None
    for line in response_text.split('\n'):
        line = line.strip()
        m = re.match(r'^(?:No\.?\s*)?([1-9])\.\s+(.+)', line)
        if m:
            if current_meta:
                results.append((current_meta, current_char))
            current_meta, current_char = m.group(2).strip(), None
            continue
        c = re.match(r'[→\-]\s*Karakter:\s*(\d+)', line)
        if c and current_meta:
            current_char = int(c.group(1))
    if current_meta:
        results.append((current_meta, current_char))
    if not results:
        candidates = [l.strip() for l in response_text.split('\n')
                      if l.strip() and len(l.strip()) > 80
                      and not l.strip().startswith(('→','-','#','=','*','⚖'))]
        results = [(c, len(c)) for c in candidates[:limit]]
    return results[:limit]


def parse_panel_score(response_text: str) -> str:
    panel = ""
    for line in response_text.split('\n'):
        if any(k in line for k in ["SpamBrain","E-E-A-T","Helpful","Benchmark","Verdict","VERDICT","⚖️"]):
            panel += f"{line.strip()}\n"
    return panel.strip()
