"""
FAQ Service — Clean Business Logic Layer.

Handles:
  - Mapped FAQ Groups for domains
  - Exact match FAQ search
  - Alias match FAQ search
  - Cosine-similarity vector match in Python
  - Failed (unanswered) questions lifecycle
  - Resolving / Promoting failed questions (either new FAQ or alias addition)
"""
import uuid
import logging
from datetime import datetime
from typing import Optional, List, Dict, Any

from core.config import FAQ_CONFIDENCE_THRESHOLD

logger = logging.getLogger("chatbot.faq_service")


# ── Domain Mapped FAQs ────────────────────────────────────────────────────────
def get_domain_faqs(domain_id: str, db) -> List[Dict[str, Any]]:
    """
    Retrieves all FAQs mapped directly to the domain.
    """
    faqs = []
    try:
        docs = db.collection("faqs").where("domain_id", "==", domain_id).stream()
        for doc in docs:
            data = doc.to_dict()
            data["id"] = doc.id
            faqs.append(data)
    except Exception as e:
        logger.error(f"Error fetching FAQs for domain {domain_id}: {e}")
    return faqs


# ── Match Logic ───────────────────────────────────────────────────────────────

def search_exact_match(faqs: List[Dict[str, Any]], query: str) -> Optional[Dict[str, Any]]:
    """Scans for an exact lowercase question match."""
    q_clean = query.strip().lower()
    for faq in faqs:
        if faq.get("question", "").strip().lower() == q_clean:
            return faq
    return None


def search_alias_match(faqs: List[Dict[str, Any]], query: str) -> Optional[Dict[str, Any]]:
    """Scans for an exact lowercase match on any FAQ alias."""
    q_clean = query.strip().lower()
    for faq in faqs:
        aliases = faq.get("aliases", [])
        if any(alias.strip().lower() == q_clean for alias in aliases):
            return faq
    return None


async def search_similarity_match(
    domain_id: str,
    faqs: List[Dict[str, Any]],
    query: str,
    db
) -> Optional[Dict[str, Any]]:
    """
    Queries ChromaDB for the domain's vector index and returns the highest similarity match
    present in the candidate list.
    """
    candidate_ids = {f["id"] for f in faqs}
    if not candidate_ids:
        return None

    try:
        from services.vector_service import get_collection
        collection = get_collection(domain_id, db)
        count = collection.count()
        if count == 0:
            return None
        results = collection.query(query_texts=[query], n_results=min(5, count))
        if not results or not results["documents"] or not results["documents"][0]:
            return None

        for idx, faq_id in enumerate(results["ids"][0]):
            distance = results["distances"][0][idx]
            # With cosine distance metric: distance in [0, 2], 0 = identical
            # Cosine similarity = 1 - cosine_distance
            confidence = max(0.0, min(1.0, 1.0 - distance))
            logger.info(f"Chroma similarity candidate: ID={faq_id}, distance={distance:.4f}, confidence={confidence:.4f} (threshold={FAQ_CONFIDENCE_THRESHOLD})")
            if confidence >= FAQ_CONFIDENCE_THRESHOLD and faq_id in candidate_ids:
                matched_faq = next((f for f in faqs if f["id"] == faq_id), None)
                if matched_faq:
                    matched_faq_copy = dict(matched_faq)
                    matched_faq_copy["confidence_score"] = confidence
                    logger.info(f"Chroma similarity match found! ID: {faq_id}, Score: {confidence:.4f}")
                    return matched_faq_copy
    except Exception as e:
        logger.error(f"Chroma similarity search failed for domain {domain_id}: {e}")

    return None


# ── Failed Question Management ────────────────────────────────────────────────

async def store_failed_question(
    db,
    domain_id: str,
    subscriber_uid: str,
    query: str,
    failed_response: str = "",
    session_id: Optional[str] = None,
) -> str:
    """Saves an unanswered visitor question to Firestore."""
    fq_id = str(uuid.uuid4())
    doc = {
        "id": fq_id,
        "domain_id": domain_id,
        "subscriber_uid": subscriber_uid,
        "query": query,
        "customer_question": query,
        "failed_response": failed_response or "",
        "ai_response": failed_response or "",
        "session_id": session_id or "",
        "status": "pending",  # pending | resolved | added_to_faq
        "created_at": datetime.utcnow().isoformat(),
    }
    try:
        db.collection("failed_questions").document(fq_id).set(doc)
        logger.info(f"Stored failed question {fq_id} for domain {domain_id}")
    except Exception as e:
        logger.error(f"Failed to store failed question: {e}")
    return fq_id


async def notify_subscriber(
    db,
    subscriber_uid: str,
    message: str,
    notification_type: str = "failed_question",
) -> None:
    """Writes a notification document for the subscriber's listener."""
    try:
        notif_id = str(uuid.uuid4())
        doc = {
            "id": notif_id,
            "type": notification_type,
            "message": message,
            "read": False,
            "created_at": datetime.utcnow().isoformat(),
        }
        db.collection("notifications").document(subscriber_uid)\
          .collection("items").document(notif_id).set(doc)
        logger.info(f"Notification written for subscriber {subscriber_uid}")
    except Exception as e:
        logger.error(f"Failed to write notification for {subscriber_uid}: {e}")


# ── Promotion & Self-Improvement ──────────────────────────────────────────────

async def promote_to_faq(
    db,
    failed_question_id: str,
    group_id: str,
    answer: Optional[str] = None,
    existing_faq_id: Optional[str] = None,
    provider: str = "openai"
) -> Optional[str]:
    """
    Promotes a failed question:
    - If existing_faq_id is provided: Adds the failed question as an alias.
    - Otherwise: Creates a new FAQ in the group, generating embeddings.
    """
    try:
        fq_snap = db.collection("failed_questions").document(failed_question_id).get()
        if not fq_snap.exists:
            logger.warning(f"Failed question {failed_question_id} not found.")
            return None

        fq_data = fq_snap.to_dict()
        query = fq_data["query"]
        subscriber_uid = fq_data["subscriber_uid"]

        if existing_faq_id:
            # Option B: Add as alias to existing FAQ
            faq_ref = db.collection("faqs").document(existing_faq_id)
            faq_snap = faq_ref.get()
            if not faq_snap.exists:
                logger.warning(f"Existing FAQ {existing_faq_id} not found.")
                return None

            faq_data = faq_snap.to_dict()
            aliases = faq_data.get("aliases", [])
            if query not in aliases:
                aliases.append(query)
                faq_ref.update({"aliases": aliases})

                # Record in faq_aliases collection
                alias_id = str(uuid.uuid4())
                db.collection("faq_aliases").document(alias_id).set({
                    "id": alias_id,
                    "faq_id": existing_faq_id,
                    "alias": query,
                    "subscriber_uid": subscriber_uid,
                    "created_at": datetime.utcnow().isoformat()
                })
                logger.info(f"Added alias '{query}' to FAQ {existing_faq_id}")
            faq_id = existing_faq_id
        else:
            # Option A: Create new FAQ
            faq_id = str(uuid.uuid4())

            # Generate embedding vector
            from services.ai_service import generate_embedding
            text_to_embed = f"Question: {query}\nAnswer: {answer}"
            embedding = []
            try:
                embedding = await generate_embedding(text_to_embed, provider)
            except Exception as e:
                logger.error(f"Failed to generate FAQ embedding during promotion: {e}")

            faq_doc = {
                "id": faq_id,
                "group_id": group_id,
                "subscriber_uid": subscriber_uid,
                "question": query,
                "answer": answer,
                "aliases": [],
                "embedding": embedding,
                "created_at": datetime.utcnow().isoformat(),
            }
            db.collection("faqs").document(faq_id).set(faq_doc)
            logger.info(f"Created new FAQ {faq_id} in group {group_id}")

        # Mark as resolved
        db.collection("failed_questions").document(failed_question_id).update({
            "status": "added_to_faq",
            "faq_id": faq_id,
            "resolved_at": datetime.utcnow().isoformat(),
        })
        return faq_id

    except Exception as e:
        logger.error(f"Failed to promote question {failed_question_id}: {e}")
        return None


# Heuristic Spam Protection

def is_spam_query(query: str) -> bool:
    """Heuristic spam detector."""
    if not query:
        return False
    q_clean = query.strip().lower()
    
    # 1. Excessive length
    if len(query) > 1000:
        return True
        
    # 2. Check for typical spam keywords
    spam_keywords = [
        "free bitcoin", "buy crypto", "make money online", "viagra", 
        "dating online", "cheap valium", "casino online", "lottery winner", 
        "sexy girls", "hot single", "work from home money", "pill supplier",
        "cheap pharmacy", "increase traffic fast", "earn passive income"
    ]
    if any(keyword in q_clean for keyword in spam_keywords):
        return True
        
    # 3. Check for repetitive character sequences (e.g. "aaaaa" or "asdfasdfasdf")
    import re
    if re.search(r'(.)\1{4,}', q_clean):  # 5 or more same chars repeated
        return True
        
    # 4. Check for links / markdown / URL spam
    if any(url_indicator in q_clean for url_indicator in ["http://", "https://", "[url=", "<a href="]):
        return True
        
    return False

async def store_spam_question(db, domain_id: str, subscriber_uid: str, query: str):
    """Saves a spam question block event to Firestore."""
    try:
        # Search if this question already exists in spam_questions for this domain
        spam_docs = db.collection("spam_questions")\
                      .where("domain_id", "==", domain_id)\
                      .where("customer_question", "==", query).get()
        if len(spam_docs) > 0:
            doc_ref = db.collection("spam_questions").document(spam_docs[0].id)
            doc_data = spam_docs[0].to_dict()
            doc_ref.update({
                "spam_count": doc_data.get("spam_count", 0) + 1,
                "last_asked_at": datetime.utcnow().isoformat()
            })
        else:
            spam_id = str(uuid.uuid4())
            db.collection("spam_questions").document(spam_id).set({
                "id": spam_id,
                "domain_id": domain_id,
                "subscriber_uid": subscriber_uid,
                "customer_question": query,
                "spam_count": 1,
                "last_asked_at": datetime.utcnow().isoformat()
            })
        logger.info(f"Logged spam question for domain {domain_id}")
    except Exception as e:
        logger.error(f"Failed to store spam question: {e}")


# ── Linear Query Resolution Pipeline ─────────────────────────────────────────

async def resolve_query(
    domain_id: str,
    subscriber_uid: str,
    query: str,
    session_id: Optional[str],
    db,
    provider: str = "openai",
    custom_prompt: Optional[str] = None,
    domain: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
    """
    Core Pipeline Sequence:
    0. Spam Detection
    1. FAQ Exact Match
    2. FAQ Alias Match
    3. Similarity Match (in-memory)
    4. Cloud AI Fallback (OpenAI/Gemini)
    5. Failed Question Queue (fallback)
    """
    if is_spam_query(query):
        await store_spam_question(db, domain_id, subscriber_uid, query)
        return {
            "response": "⚠ Message blocked due to suspicious activity.",
            "source": "spam"
        }

    # Fetch all FAQs for the domain
    all_faqs = get_domain_faqs(domain_id, db)

    # Separate into direct domain-based FAQs (group_id is None, "", or "default") and grouped FAQs
    domain_faqs = []
    grouped_faqs = []
    for f in all_faqs:
        gid = f.get("group_id")
        if not gid or gid == "default":
            domain_faqs.append(f)
        else:
            grouped_faqs.append(f)

    # 1. Search Domain-based FAQs first
    exact_match = search_exact_match(domain_faqs, query)
    if exact_match:
        logger.info(f"Query '{query}' resolved via Domain-based FAQ Exact Match.")
        return {"response": exact_match["answer"], "source": "faq", "faq_id": exact_match["id"]}

    alias_match = search_alias_match(domain_faqs, query)
    if alias_match:
        logger.info(f"Query '{query}' resolved via Domain-based FAQ Alias Match.")
        return {"response": alias_match["answer"], "source": "faq", "faq_id": alias_match["id"]}

    similarity_match = await search_similarity_match(domain_id, domain_faqs, query, db)
    if similarity_match:
        logger.info(f"Query '{query}' resolved via Domain-based FAQ Similarity Match.")
        return {"response": similarity_match["answer"], "source": "faq", "faq_id": similarity_match["id"]}

    # 2. Search Grouped FAQs next (group_id with domain id faq search)
    exact_match = search_exact_match(grouped_faqs, query)
    if exact_match:
        logger.info(f"Query '{query}' resolved via Grouped FAQ Exact Match.")
        return {"response": exact_match["answer"], "source": "faq", "faq_id": exact_match["id"]}

    alias_match = search_alias_match(grouped_faqs, query)
    if alias_match:
        logger.info(f"Query '{query}' resolved via Grouped FAQ Alias Match.")
        return {"response": alias_match["answer"], "source": "faq", "faq_id": alias_match["id"]}

    similarity_match = await search_similarity_match(domain_id, grouped_faqs, query, db)
    if similarity_match:
        logger.info(f"Query '{query}' resolved via Grouped FAQ Similarity Match.")
        return {"response": similarity_match["answer"], "source": "faq", "faq_id": similarity_match["id"]}

    # 3. Cloud AI Fallback (OpenAI/Gemini) if limit not reached
    ai_limit = 100
    ai_count = 0
    sub_name = subscriber_uid
    try:
        sub_snap = db.collection("users").document(subscriber_uid).get()
        if sub_snap.exists:
            sub_data = sub_snap.to_dict()
            ai_limit = sub_data.get("ai_call_limit", 100)
            ai_count = sub_data.get("ai_call_count", 0)
            sub_name = sub_data.get("display_name", subscriber_uid)
    except Exception as e:
        logger.error(f"Failed to fetch subscriber AI limits: {e}")

    if ai_count < ai_limit:
        # Call live AI to answer
        try:
            from services.ai_service import generate_chatbot_response, generate_embedding
            # Construct context from existing domain FAQs
            context_texts = []
            for f in domain_faqs[:5]:
                context_texts.append(f"Q: {f.get('question')}\nA: {f.get('answer')}")
            context = "\n\n".join(context_texts)

            ai_res = await generate_chatbot_response(
                query=query,
                context=context,
                custom_prompt=custom_prompt
            )
            ai_answer = ai_res.get("response") if ai_res else None

            if ai_answer:
                # Save answer as new FAQ in Firestore
                faq_id = str(uuid.uuid4())
                try:
                    text_to_embed = f"Question: {query}\nAnswer: {ai_answer}"
                    embedding = await generate_embedding(text_to_embed, provider)
                except Exception as emb_e:
                    logger.error(f"Failed to generate embedding for auto-created FAQ: {emb_e}")
                    embedding = []

                faq_doc = {
                    "id": faq_id,
                    "domain_id": domain_id,
                    "question": query,
                    "answer": ai_answer,
                    "group_id": "default",
                    "aliases": [],
                    "keywords": [],
                    "embedding": embedding,
                    "created_at": datetime.utcnow().isoformat(),
                    "is_generated": True
                }
                db.collection("faqs").document(faq_id).set(faq_doc)

                # Index in ChromaDB
                try:
                    from services.vector_service import get_collection
                    collection = get_collection(domain_id, db)
                    text_to_embed = f"Question: {query}\nAnswer: {ai_answer}"
                    collection.add(
                        ids=[faq_id],
                        documents=[text_to_embed],
                        metadatas=[{
                            "faq_id": faq_id,
                            "question": query,
                            "answer": ai_answer
                        }]
                    )
                    logger.info(f"Auto-created FAQ indexed in Chroma: {faq_id}")
                except Exception as chroma_e:
                    logger.error(f"Failed to index auto-created FAQ in Chroma: {chroma_e}")

                # Increment subscriber's AI call count
                from google.cloud import firestore
                try:
                    db.collection("users").document(subscriber_uid).update({
                        "ai_call_count": firestore.Increment(1)
                    })
                except Exception as inc_e:
                    logger.error(f"Failed to increment AI call count: {inc_e}")

                return {"response": ai_answer, "source": "live_ai"}
        except Exception as ai_err:
            logger.error(f"Live AI fallback failed: {ai_err}")

    # 4. Fallback message if limit reached or AI failed
    fallback_text = "Sorry, we could not find an answer."
    helpline = ""
    if domain:
        fallback_text = domain.get("fallback_message") or fallback_text
        helpline = domain.get("helpline_number") or ""
        
    if helpline:
        if "contact support" in fallback_text.lower():
            fallback_msg = f"{fallback_text} {helpline}"
        else:
            fallback_msg = f"{fallback_text}\n\nPlease contact support: {helpline}"
    else:
        fallback_msg = fallback_text

    # Store in failed question queue
    fq_id = await store_failed_question(db, domain_id, subscriber_uid, query, fallback_msg, session_id)
    
    # Notify only the admin (no alert to subscriber)
    try:
        admin_msg = f"Subscriber '{sub_name}' has reached their AI Call Limit of {ai_limit}. Question: '{query[:50]}...'"
        admins = db.collection("users").where("role", "==", "admin").stream()
        for admin in admins:
            await notify_subscriber(
                db,
                admin.id,
                admin_msg,
                notification_type="limit_reached"
            )
    except Exception as adm_e:
        logger.error(f"Failed to alert admin: {adm_e}")

    return {"response": fallback_msg, "source": "fallback", "failed_question_id": fq_id}
