import json
import traceback
from io import BytesIO
from django.core.files.base import ContentFile
from PIL import Image
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.views.decorators.clickjacking import xframe_options_exempt

import subprocess
import sys
import os
import requests
from urllib.parse import urlparse

from .models import Company, User, Topic, FAQ, CustomParameter, Escalation, ChatMessage, Product, VisitorSessionTopic, VoiceReply
from django.utils import timezone
from .ai_service import get_gemini_response, get_openai_response
from .utils.visitor_id import generate_unique_visitor_code
import threading
import time
from concurrent.futures import ThreadPoolExecutor
# SMS delivery removed — notifications via SMS/Twilio deprecated in this deployment

# Thread pool executor replaced with dynamic threads for database connection safety


# Decorator: Superuser Restricted
def superuser_required(view_func):
    @login_required
    def _wrapped_view(request, *args, **kwargs):
        if request.user.role == User.Role.SUPERUSER or request.user.is_superuser:
            return view_func(request, *args, **kwargs)
        raise PermissionDenied("You do not have permission to access the superuser console.")
    return _wrapped_view

# Decorator: Client Admin Restricted
def admin_required(view_func):
    @login_required
    def _wrapped_view(request, *args, **kwargs):
        if request.user.role == User.Role.ADMIN or request.user.is_superuser:
            if not request.user.is_superuser and not request.user.company:
                raise PermissionDenied("You are not associated with any company dashboard.")
            return view_func(request, *args, **kwargs)
        raise PermissionDenied("Admin console access denied.")
    return _wrapped_view

# Decorator: Agent Restricted
def agent_required(view_func):
    @login_required
    def _wrapped_view(request, *args, **kwargs):
        if request.user.role == User.Role.AGENT:
            if not request.user.company:
                raise PermissionDenied("You are not associated with any company.")
            return view_func(request, *args, **kwargs)
        raise PermissionDenied("Agent desk access denied.")
    return _wrapped_view

# Decorator: Agent or Admin Restricted
def agent_or_admin_required(view_func):
    @login_required
    def _wrapped_view(request, *args, **kwargs):
        if request.user.role in [User.Role.AGENT, User.Role.ADMIN] or request.user.is_superuser:
            if not request.user.is_superuser and not request.user.company:
                raise PermissionDenied("You are not associated with any company.")
            return view_func(request, *args, **kwargs)
        raise PermissionDenied("Access denied.")
    return _wrapped_view

def get_widget_embed_attrs(company, request):
    """Build data-* attributes for the widget embed script tag using absolute media URLs."""
    attrs = {
        'data-company-id': str(company.id),
        'data-position': company.widget_position,
        # provide enabled positions (comma-separated). If not set, fall back to single position
        'data-positions': (company.widget_positions or company.widget_position),
        'data-bottom-offset': str(company.widget_bottom_offset),
        'data-side-offset': str(company.widget_side_offset),
        'data-button-size': str(company.widget_button_size),
        'data-panel-width': str(company.widget_panel_width),
        'data-panel-height': str(company.widget_panel_height),
    }
    # provide a separate chatbot button icon; fallback to company.icon
    try:
        if company.chatbot_icon:
            attrs['data-chatbot-icon-url'] = request.build_absolute_uri(company.chatbot_icon.url)
        elif company.icon:
            attrs['data-chatbot-icon-url'] = request.build_absolute_uri(company.icon.url)
    except Exception:
        # fallback: attempt to build a simple path
        if company.chatbot_icon:
            attrs['data-chatbot-icon-url'] = getattr(company.chatbot_icon, 'url', '')
        elif company.icon:
            attrs['data-chatbot-icon-url'] = getattr(company.icon, 'url', '')
    return attrs


def _compress_image_file(uploaded_file, target_kb=15, max_size=(800,800)):
    """Compress uploaded image to be approximately <= target_kb kilobytes.
    Returns a django ContentFile ready to save.
    """
    try:
        img = Image.open(uploaded_file)
    except Exception:
        return None

    # Convert to RGB for consistent JPEG output
    if img.mode in ('RGBA', 'LA'):
        background = Image.new('RGB', img.size, (255,255,255))
        background.paste(img, mask=img.split()[3])
        img = background
    else:
        img = img.convert('RGB')

    # resize if too large — use a resampling filter compatible with Pillow versions
    try:
        resample_filter = Image.Resampling.LANCZOS
    except AttributeError:
        # older Pillow exposes LANCZOS or ANTIALIAS at module level
        resample_filter = getattr(Image, 'LANCZOS', getattr(Image, 'ANTIALIAS', None))

    if resample_filter is not None:
        img.thumbnail(max_size, resample=resample_filter)
    else:
        # final fallback: call thumbnail without explicit resample
        img.thumbnail(max_size)

    quality = 85
    step = 5
    target_bytes = target_kb * 1024

    for q in range(quality, 9, -step):
        buf = BytesIO()
        img.save(buf, format='JPEG', quality=q, optimize=True)
        data = buf.getvalue()
        if len(data) <= target_bytes or q <= 20:
            name = getattr(uploaded_file, 'name', 'agent_photo.jpg')
            if not name.lower().endswith('.jpg') and not name.lower().endswith('.jpeg'):
                name = name.rsplit('.', 1)[0] + '.jpg'
            return ContentFile(data, name=name)
    return None

def check_custom_parameters(company, user_message):
    """
    Check if user message matches any custom parameters (comma-separated keywords supported).
    Returns (matched, response) tuple. matched=True if a parameter matched.
    """
    user_message_lower = user_message.lower()
    
    # Get all custom parameters for the company
    parameters = company.custom_parameters.all()
    
    for param in parameters:
        if not param.keyword:
            continue
        # Support comma-separated keywords
        keywords = [k.strip().lower() for k in param.keyword.split(',')]
        for kw in keywords:
            if kw and kw in user_message_lower:
                return True, param.response
    
    return False, None


def check_voice_replies(company, user_message):
    """
    Check if user message matches any voice replies (comma-separated keywords supported).
    Returns (matched, voice_reply_obj) tuple. matched=True if a voice reply matched.
    """
    if not user_message:
        return False, None
    user_message_lower = user_message.lower()
    
    # Get all voice replies for the company
    replies = company.voice_replies.all()
    
    for reply in replies:
        if not reply.keyword:
            continue
        # Support comma-separated keywords
        keywords = [k.strip().lower() for k in reply.keyword.split(',')]
        for kw in keywords:
            if kw and kw in user_message_lower:
                return True, reply
    
    return False, None


def get_or_update_session_topic(company, visitor_id, user_message=None):
    from django.utils import timezone
    from datetime import timedelta
    
    # 1. Clean up expired session topics to save database space (older than 10 mins)
    ten_minutes_ago = timezone.now() - timedelta(minutes=10)
    VisitorSessionTopic.objects.filter(updated_at__lt=ten_minutes_ago).delete()
    
    if not visitor_id:
        return None
        
    session_topic = VisitorSessionTopic.objects.filter(company=company, visitor_id=visitor_id).first()
    
    # Safety check if session is expired but not yet deleted
    if session_topic and session_topic.updated_at < ten_minutes_ago:
        session_topic.delete()
        session_topic = None
        
    if user_message:
        user_message_lower = user_message.lower()
        matched_param = None
        
        # Search through company's CustomParameters for matching keywords
        custom_params = company.custom_parameters.all()
        for cp in custom_params:
            if not cp.keyword:
                continue
            keywords = [k.strip().lower() for k in cp.keyword.split(',')]
            for kw in keywords:
                if kw and kw in user_message_lower:
                    matched_param = cp
                    break
            if matched_param:
                break
                
        if matched_param:
            # Match found! Use the primary (first) keyword
            primary_keyword = [k.strip() for k in matched_param.keyword.split(',')][0]
            if not session_topic:
                session_topic = VisitorSessionTopic.objects.create(
                    company=company,
                    visitor_id=visitor_id,
                    current_topic=primary_keyword
                )
            else:
                session_topic.current_topic = primary_keyword
                session_topic.save()
        elif session_topic:
            # Touch the timestamp to extend the 10 minutes session active status
            session_topic.save()
            
    return session_topic.current_topic if session_topic else None


# Helper to build effective system prompt with multiple topics guardrails
def get_effective_prompt(company, user_message=None, visitor_id=None):
    system_prompt = company.system_prompt or ""
    
    # Inject active session topic context if available
    active_topic = get_or_update_session_topic(company, visitor_id, user_message)
    if active_topic:
        system_prompt += (
            f"\n\n[CURRENT CONVERSATION TOPIC]\n"
            f"The visitor is currently discussing/interested in: '{active_topic}'. "
            f"If the visitor asks for a demo, pricing, features, or details without specifying "
            f"which software/product/service, assume they are asking about '{active_topic}' and reply accordingly."
        )

    
    # Inject Custom Parameters as Knowledge Base for semantic matching
    custom_params = company.custom_parameters.all()
    if custom_params.exists():
        system_prompt += "\n\n[COMPANY KNOWLEDGE BASE]\nUse the following information to answer user queries if relevant (match semantically even if the language is different):\n"
        for cp in custom_params:
            system_prompt += f"- Keywords: {cp.keyword}\n  Response: {cp.response}\n"

    # Inject Forms for AI intent routing
    forms = company.forms.all()
    if forms.exists():
        system_prompt += "\n\n[AVAILABLE FORMS]\n"
        system_prompt += (
            "If the visitor explicitly intends to fill out a custom form or perform the action it describes, "
            "you must ask for all the required fields of the form together in a single message. Do not write them in a single line or paragraph. "
            "You MUST display the required fields as a numbered/serial list (e.g.,\n"
            "1. Name\n"
            "2. Address\n"
            "3. Phone Number\n"
            "etc.).\n"
            "You MUST speak in the language of the conversation (e.g., Bengali if they speak Bengali, English if English). "
            "Provide a polite introduction explaining that they need to provide these details (e.g., 'অবশ্যই, আপনি যদি এই ফর্মটি পূরণ করতে চান, "
            "তাহলে অনুগ্রহ করে নিচের তথ্যগুলো দিন:' in Bengali, or 'Yes, of course, if you want to submit this form, "
            "please fill up the following information:' in English).\n"
            "You MUST also explicitly instruct the visitor at the end of your request that they can cancel the form filling anytime "
            "(e.g., 'আপনি যদি বাতিল করতে চান, তাহলে \"Cancel\" বা \"Not now\" লিখুন' in Bengali, or 'Type \"Cancel\" "
            "or \"Not now\" to cancel the form' in English).\n"
            "If the user decides to cancel or says 'Cancel' or 'Not now', respect their request, stop asking for the "
            "form fields, and reply with a normal conversational response confirming that the form submission has been cancelled.\n"
            "Once you have collected all the required fields for that form, you MUST trigger the form submission "
            "by returning a personalized, polite \"Thank You\" message (in the language of the conversation, e.g. Bengali or English) "
            "followed by EXACTLY and ONLY this JSON block on a new line (do not add markdown code blocks):\n"
            "TRIGGER_FORM_SUBMISSION: {\"form_id\": <id>, \"answers\": {\"<field_name_1>\": \"...\", \"<field_name_2>\": \"...\"}}\n\n"
        )
        for f in forms:
            fields_list = ", ".join([field.name for field in f.fields.all()])
            system_prompt += f"- Form ID: {f.id} | Form Name: {f.title} | Trigger Keywords: {f.keyword} | Required Fields: {fields_list}\n"
            
        # Check if visitor has already submitted any forms
        if visitor_id:
            from .models import FormSubmission
            submitted_ids = list(FormSubmission.objects.filter(form__company=company, visitor_id=visitor_id).values_list('form_id', flat=True).distinct())
            if submitted_ids:
                system_prompt += "\n[SUBMITTED FORMS]\nThe visitor has already submitted the following form IDs in this chat:\n"
                for s_id in submitted_ids:
                    system_prompt += f"- Form ID: {s_id}\n"
                system_prompt += (
                    "\nCRITICAL INSTRUCTION: If the visitor tries to fill out or trigger a form whose ID is in the "
                    "[SUBMITTED FORMS] list above, you MUST first ask them: 'You have already submitted this form before. "
                    "Do you want to submit a new one?' (Speak in the conversation language, e.g. Bengali or English). "
                    "ONLY proceed to ask for details or trigger the submission if they explicitly say yes. Otherwise, do not trigger the form."
                )
    else:
        system_prompt += "\n\n[AVAILABLE FORMS]\nThere are no custom forms currently available. If the user asks for a form, inform them that it is no longer available.\n"


    # Inject Media for AI image responses
    media_items = company.media_items.all()
    if media_items.exists():
        system_prompt += "\n\n[AVAILABLE MEDIA]\nIf the user asks to see a picture or menu, and their request is conceptually similar or related to the following keywords, you MUST trigger it by returning a short, natural, and conversational response confirming the image, followed by this EXACT tag: [MEDIA:<id>]\nDo not decline or apologize if it's a close semantic match; just return the tag along with a natural response.\n"
        for m in media_items:
            system_prompt += f"- ID: {m.id} | Tags: {m.tags}\n"

    # Fetch data from E-commerce API if configured
    if hasattr(company, 'ecom_api_url') and company.ecom_api_url and company.ecom_api_key:
        try:
            import requests
            api_url = company.ecom_api_url
            if not api_url.endswith('/'):
                api_url += '/'
            
            catalog_url = api_url + 'api/bot/catalog/'
            headers = {'Authorization': f'Bearer {company.ecom_api_key}'}
            # Short timeout to prevent blocking replies
            cat_res = requests.get(catalog_url, headers=headers, timeout=2)
            if cat_res.status_code == 200:
                res_json = cat_res.json()
                products = res_json.get('products', [])
                delivery_locations = res_json.get('delivery_locations', [])
            else:
                products, delivery_locations = [], []

            if products:
                system_prompt += "\n\n[LIVE E-COMMERCE PRODUCTS]\nHere are the live products from the connected e-commerce store. Use this data to answer user queries about products, prices, and availability:\n"
                for p in products[:50]:  # Limit to 50 products to avoid huge prompts
                    p_name = p.get('name', 'Unknown')
                    p_price = p.get('price', 'N/A')
                    p_cat = p.get('category', 'N/A')
                    p_stock = p.get('stock', 'Unknown')
                    p_link = f"{api_url}{p.get('category_slug', 'all')}/{p.get('slug', '')}/"
                    system_prompt += f"- {p_name} (Category: {p_cat}) | Price: {p_price} | Stock: {p_stock} | Link: {p_link} | Product ID: {p.get('id')}\n"
                
                if user_message and any(cmd in user_message.lower() for cmd in ['api er data daw', 'api er data dhoro', 'give api data', 'catch api data', 'show api data']):
                    system_prompt += "\n\nCRITICAL INSTRUCTION: The user just commanded you to show the API data. You MUST reply by displaying the details of the products listed above to prove that you have successfully received the live API data. Speak in the user's language (Bengali or English)."
                
                system_prompt += "\n\n[E-COMMERCE ORDERING]\n"
                system_prompt += "To help a user place an order for a product from the catalog, you must collect this information from them:\n"
                
                if delivery_locations:
                    loc_str = ", ".join([f"{loc['name']} ({loc['charge']} TK)" for loc in delivery_locations])
                    system_prompt += f"1. Name\n2. Phone Number\n3. Delivery Address (Ask where they want the delivery. Inform them of the delivery charges: {loc_str}).\n"
                else:
                    system_prompt += "1. Name\n2. Phone Number\n3. Delivery Address (Ask whether it is inside Dhaka or outside Dhaka. Inform them that the delivery charge is typically 60 TK inside Dhaka, 120 TK outside Dhaka).\n"
                    
                system_prompt += "CRITICAL: Do not ask for the user's email address. Do not show the internal 'Product ID' to the user in your normal text replies; keep it hidden and use it only internally for the JSON payload.\n"
                system_prompt += "Once you have ALL this information, you MUST trigger the order by returning EXACTLY and ONLY this JSON block (do not add markdown code blocks or any other text):\n"
                system_prompt += "TRIGGER_ECOM_ORDER: {\"customer_name\": \"...\", \"customer_phone\": \"...\", \"customer_address\": \"...\", \"delivery_charge\": 120, \"items\": [{\"product_id\": 123, \"quantity\": 1}]}\n"
                
                if delivery_locations:
                    system_prompt += "Set the `delivery_charge` field in the JSON to the exact charge amount based on their chosen delivery location. Ensure the correct product_id from the catalog is used.\n"
                else:
                    system_prompt += "Replace '120' with '60' if the delivery address is inside Dhaka. Ensure the correct product_id from the catalog is used.\n"
                    
        except Exception:
            pass

    # Inject relevant products dynamically
    system_prompt += "\n\n[PRODUCT INVENTORY]\n"
    
    # Try to extract categories from URLs dynamically via sitemap
    try:
        from urllib.parse import urlparse
        import requests
        import xml.etree.ElementTree as ET
        import re
        from bs4 import BeautifulSoup
        import concurrent.futures

        product_urls = []
        headers = {'User-Agent': 'Mozilla/5.0'}
        
        # Fetch products from database (populated by sitemap_spider.py in the background)
        product_urls = list(company.products.values_list('url', flat=True)[:1000])

        categories = set()
        for u in product_urls[:100]:
            path_parts = [p for p in urlparse(u).path.split('/') if p and not p.endswith('.html') and not p.endswith('.php') and len(p) > 2]
            if len(path_parts) >= 1:
                categories.add(path_parts[0].replace('-', ' ').title())
        
        if categories:
            cat_str = ", ".join(list(categories)[:15])
            system_prompt += f"The company has products in these live categories: {cat_str}.\n"

        matched = False
        target_urls = []
        if user_message:
            user_words = [w.lower() for w in user_message.lower().split() if len(w) > 3]
            if user_words:
                for u in product_urls:
                    u_lower = u.lower()
                    if any(w in u_lower for w in user_words):
                        target_urls.append(u)
                        if len(target_urls) >= 4:
                            break
                            
        db_matched_products = []
        if not target_urls and user_message:
            from django.db.models import Q
            user_words = [w for w in user_message.lower().split() if len(w) > 3]
            if user_words:
                query = Q()
                for w in user_words:
                    query |= Q(title__icontains=w) | Q(brand__icontains=w) | Q(description__icontains=w)
                db_matched_products = list(company.products.filter(query).distinct()[:4])
        
        if db_matched_products:
            matched = True
            system_prompt += "Here are some specific products matching the user's query:\n"
            for p in db_matched_products:
                system_prompt += f"- Name: {p.title}\n  Price: {p.price}\n  Brand: {p.brand}\n  Link: {p.url}\n"
        elif target_urls:
            matched = True
            system_prompt += "Here are some specific live products retrieved from the site matching the query:\n"
            
            def fetch_product_info(purl):
                try:
                    p_resp = requests.get(purl, headers=headers, timeout=1.5)
                    soup = BeautifulSoup(p_resp.text, 'html.parser')
                    title = soup.find('meta', property='og:title')
                    title = title['content'] if title else (soup.title.string if soup.title else 'No Title')
                    price = soup.find('meta', property='product:price:amount')
                    price = price['content'] if price else None
                    brand = soup.find('meta', property='product:brand')
                    brand = brand['content'] if brand else None
                    
                    if not price or not brand:
                        import json
                        for script in soup.find_all('script', type='application/ld+json'):
                            try:
                                data = json.loads(script.string)
                                if isinstance(data, dict):
                                    data = [data]
                                for item in data:
                                    if item.get('@type') == 'Product':
                                        if not price and item.get('offers') and isinstance(item.get('offers'), dict):
                                            price = item['offers'].get('price')
                                        if not brand and item.get('brand'):
                                            brand = item['brand'].get('name') if isinstance(item['brand'], dict) else item['brand']
                            except Exception:
                                pass
                    res = f"- Name: {title}\n  Price: {price or 'Check link'}\n  Brand: {brand or 'Unknown'}\n  Link: {purl}\n"
                    return res
                except Exception:
                    return f"- Link: {purl}\n"

            with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
                results = executor.map(fetch_product_info, target_urls)
                for res in results:
                    system_prompt += res
                    
        if not matched:
            sample_products = company.products.all().order_by('-id')[:5]
            if sample_products:
                system_prompt += "Here is a small sample of our products (ask the user to be specific if they want something else):\n"
                for p in sample_products:
                    system_prompt += f"- Name: {p.title}\n  Price: {p.price}\n  Link: {p.url}\n"
            elif product_urls:
                system_prompt += "Here are some live product links from our catalog:\n"
                for u in product_urls[:5]:
                    system_prompt += f"- Link: {u}\n"
            else:
                system_prompt += "Currently, no products are synchronized.\n"

    except Exception as e:
        import logging
        logging.getLogger(__name__).error(f"Error in prompt generation: {e}")

    topics = company.topics.all()
    if topics.exists():
        topics_list = ", ".join([t.name for t in topics])
        guardrail = (
            f"\n[STRICT TOPIC CONSTRAINT] You are a chatbot for a company that exclusively deals with the following topics: '{topics_list}'. "
            "You must strictly only answer questions that are relevant to these topics. "
            "If a user asks about anything else (e.g. general knowledge, math, other products, coding, or unrelated queries), "
            "you must politely and concisely refuse to answer, explaining that you can only answer questions related to "
            f"'{topics_list}'."
        )
        system_prompt += guardrail
    return system_prompt

# ----------------- PUBLIC VIEWS -----------------

def home_view(request):
    if request.user.is_authenticated:
        if request.user.role == User.Role.SUPERUSER or request.user.is_superuser:
            return redirect('superuser_dashboard')
        elif request.user.role == User.Role.ADMIN:
            return redirect('admin_dashboard')
        elif request.user.role == User.Role.AGENT:
            return redirect('agent_dashboard')
            
    from .models import SiteSettings, HeroSlide
    import re
    settings = SiteSettings.objects.first()
    english_title = "Increase Engagement with Your Customers"
    if settings:
        has_bengali = bool(re.search(r'[\u0980-\u09FF]', settings.hero_title or ''))
        if settings.hero_title == "Increase Your Sales" or not settings.hero_title or has_bengali:
            settings.hero_title = english_title
            settings.save()
    else:
        settings = SiteSettings.objects.create(
            hero_title=english_title
        )
    hero_slides = HeroSlide.objects.all()
    return render(request, 'public/home.html', {'settings': settings, 'hero_slides': hero_slides})

def login_view(request):
    if request.user.is_authenticated:
        if request.user.role == User.Role.SUPERUSER or request.user.is_superuser:
            return redirect('superuser_dashboard')
        elif request.user.role == User.Role.ADMIN:
            return redirect('admin_dashboard')
        elif request.user.role == User.Role.AGENT:
            return redirect('agent_dashboard')
        return redirect('home')
        
    error_message = None
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)
        
        if user is not None:
            login(request, user)
            if user.role == User.Role.SUPERUSER or user.is_superuser:
                messages.success(request, f"Welcome to Superuser Console, {user.username}!")
                return redirect('superuser_dashboard')
            elif user.role == User.Role.ADMIN:
                messages.success(request, f"Welcome to Admin Console, {user.username}!")
                return redirect('admin_dashboard')
            elif user.role == User.Role.AGENT:
                messages.success(request, f"Welcome to Agent Desk, {user.username}!")
                return redirect('agent_dashboard')
            return redirect('home')
        else:
            error_message = "Invalid username or password."
            
    return render(request, 'login.html', {'error_message': error_message})

def logout_view(request):
    logout(request)
    messages.success(request, "You have been successfully logged out.")
    return redirect('home')

# ----------------- SUPERUSER CONSOLE VIEWS -----------------

@superuser_required
def superuser_dashboard(request):
    import json
    from .models import AIModel, Lead, Escalation
    from django.db.models import Count
    from django.db.models.functions import TruncDate
    from datetime import date, timedelta

    companies = Company.objects.all().order_by('-created_at')
    gemini_count = Company.objects.filter(ai_provider='gemini').count()
    openai_count = Company.objects.filter(ai_provider='openai').count()
    host = request.get_host()
    scheme = 'https' if request.is_secure() else 'http'
    base_url = f"{scheme}://{host}"

    # Get available models by provider
    gemini_models = AIModel.objects.filter(provider='gemini').order_by('-created_at')
    openai_models = AIModel.objects.filter(provider='openai').order_by('-created_at')

    # Get recent lead captures
    leads = Lead.objects.all().order_by('-created_at')[:20]

    # ---- CHART DATA ----
    today = date.today()
    last_14_days = [today - timedelta(days=i) for i in range(13, -1, -1)]
    date_labels = [d.strftime('%b %d') for d in last_14_days]

    # Daily messages served by bots (last 14 days)
    daily_bot_qs = (
        ChatMessage.objects
        .filter(sender='bot', created_at__date__gte=last_14_days[0])
        .annotate(day=TruncDate('created_at'))
        .values('day')
        .annotate(count=Count('id'))
        .order_by('day')
    )
    daily_bot_map = {row['day'].strftime('%b %d'): row['count'] for row in daily_bot_qs}
    daily_bot_data = [daily_bot_map.get(label, 0) for label in date_labels]

    # Daily visitor messages (last 14 days)
    daily_visitor_qs = (
        ChatMessage.objects
        .filter(sender='visitor', created_at__date__gte=last_14_days[0])
        .annotate(day=TruncDate('created_at'))
        .values('day')
        .annotate(count=Count('id'))
        .order_by('day')
    )
    daily_visitor_map = {row['day'].strftime('%b %d'): row['count'] for row in daily_visitor_qs}
    daily_visitor_data = [daily_visitor_map.get(label, 0) for label in date_labels]

    # Messages per company (top 8)
    company_msg_qs = (
        Company.objects
        .annotate(msg_count=Count('messages'))
        .order_by('-msg_count')[:8]
    )
    company_labels = [c.name for c in company_msg_qs]
    company_msg_data = [c.msg_count for c in company_msg_qs]

    # Message type breakdown (bot vs visitor vs agent today)
    sender_counts = (
        ChatMessage.objects
        .filter(created_at__date=today)
        .values('sender')
        .annotate(count=Count('id'))
    )
    sender_map = {row['sender']: row['count'] for row in sender_counts}
    today_bot = sender_map.get('bot', 0)
    today_visitor = sender_map.get('visitor', 0)
    today_agent = sender_map.get('agent', 0)

    # Escalations last 14 days
    daily_esc_qs = (
        Escalation.objects
        .filter(created_at__date__gte=last_14_days[0])
        .annotate(day=TruncDate('created_at'))
        .values('day')
        .annotate(count=Count('id'))
        .order_by('day')
    )
    daily_esc_map = {row['day'].strftime('%b %d'): row['count'] for row in daily_esc_qs}
    daily_esc_data = [daily_esc_map.get(label, 0) for label in date_labels]

    # Summary counts
    total_messages = ChatMessage.objects.count()
    total_today = ChatMessage.objects.filter(created_at__date=today).count()
    total_escalations = Escalation.objects.count()
    total_leads = Lead.objects.count()

    return render(request, 'superuser/dashboard.html', {
        'companies': companies,
        'gemini_count': gemini_count,
        'openai_count': openai_count,
        'base_url': base_url,
        'gemini_models': gemini_models,
        'openai_models': openai_models,
        'leads': leads,
        # chart data serialised to JSON
        'chart_date_labels': json.dumps(date_labels),
        'chart_bot_data': json.dumps(daily_bot_data),
        'chart_visitor_data': json.dumps(daily_visitor_data),
        'chart_esc_data': json.dumps(daily_esc_data),
        'chart_company_labels': json.dumps(company_labels),
        'chart_company_data': json.dumps(company_msg_data),
        'today_bot': today_bot,
        'today_visitor': today_visitor,
        'today_agent': today_agent,
        'total_messages': total_messages,
        'total_today': total_today,
        'total_escalations': total_escalations,
        'total_leads': total_leads,
    })

@superuser_required
def company_create(request):
    from .models import AIModel, Package
    global_topics = Topic.objects.all()
    ai_models = AIModel.objects.all().order_by('provider', 'display_name')
    packages = Package.objects.all().order_by('name')
    if request.method == 'POST':
        name = request.POST.get('name')
        chatbot_name = request.POST.get('chatbot_name')
        welcome_message = request.POST.get('welcome_message', 'Hi! Welcome to our site. I am your virtual assistant. Feel free to ask me anything!')
        ai_model_id = request.POST.get('ai_model')
        api_key = request.POST.get('api_key')
        system_prompt = request.POST.get('system_prompt')
        allowed_domain = request.POST.get('allowed_domain', '').strip().lower()
        admin_username = request.POST.get('admin_username', '').strip()
        admin_password = request.POST.get('admin_password', '').strip()
        admin_email = request.POST.get('admin_email', '').strip()

        if not admin_username or not admin_password:
            messages.error(request, "Company Admin username and password are required.")
            return render(request, 'superuser/company_form.html', {
                'global_topics': global_topics,
                'ai_models': ai_models,
                'packages': packages,
                'show_admin_fields': True,
                'is_create': True,
            })

        if User.objects.filter(username=admin_username).exists():
            messages.error(request, f"Username '{admin_username}' is already taken. Choose a different username.")
            return render(request, 'superuser/company_form.html', {
                'global_topics': global_topics,
                'ai_models': ai_models,
                'packages': packages,
                'show_admin_fields': True,
                'is_create': True,
            })

        try:
            with transaction.atomic():
                selected_ai_model = None
                ai_provider = 'gemini'
                model_name = 'gemini-3.1-flash-lite'
                if ai_model_id:
                    selected_ai_model = get_object_or_404(AIModel, id=ai_model_id)
                    ai_provider = selected_ai_model.provider
                    model_name = selected_ai_model.model_name
                
                company = Company.objects.create(
                    name=name,
                    chatbot_name=chatbot_name,
                    welcome_message=welcome_message,
                    ai_provider=ai_provider,
                    model_name=model_name,
                    ai_model=selected_ai_model,
                    api_key=request.POST.get('api_key', '').strip() or None,
                    system_prompt=request.POST.get('system_prompt', '').strip() or None,
                    google_sheet_url=request.POST.get('google_sheet_url', '').strip() or None,
                    allowed_domain=allowed_domain or None,
                    auto_escalate_via_sms=(request.POST.get('auto_escalate_via_sms') == 'on'),
                    auto_escalate_every_message=(request.POST.get('auto_escalate_every_message') == 'on'),
                    show_watermark=(request.POST.get('show_watermark') == 'on'),
                    image_limit=int(request.POST.get('image_limit', 50)),
                    widget_position=request.POST.get('widget_position', 'right'),
                    fb_messenger_enabled=(request.POST.get('fb_messenger_enabled') == 'on'),
                    fb_verify_token=request.POST.get('fb_verify_token', '').strip() or None,
                    fb_page_id=request.POST.get('fb_page_id', '').strip() or None,
                    fb_page_access_token=request.POST.get('fb_page_access_token', '').strip() or None,
                    fb_typing_indicator_enabled=(request.POST.get('fb_typing_indicator_enabled') == 'on'),
                    fb_mark_seen_enabled=(request.POST.get('fb_mark_seen_enabled') == 'on'),
                    agent_fb_comments_access=(request.POST.get('agent_fb_comments_access') == 'on'),
                    whatsapp_enabled=(request.POST.get('whatsapp_enabled') == 'on'),
                    wa_phone_number_id=request.POST.get('wa_phone_number_id', '').strip() or None,
                    wa_business_account_id=request.POST.get('wa_business_account_id', '').strip() or None,
                    wa_access_token=request.POST.get('wa_access_token', '').strip() or None,
                    sitemap_url=request.POST.get('sitemap_url', '').strip() or None,
                    ecom_api_url=request.POST.get('ecom_api_url', '').strip() or None,
                    ecom_api_key=request.POST.get('ecom_api_key', '').strip() or None,
                    subscription_valid_until=request.POST.get('subscription_valid_until') or None,
                    message_limit=int(request.POST.get('message_limit') or 0),
                    package_id=request.POST.get('package') or None,
                )
                if request.FILES.get('icon'):
                    company.icon = request.FILES['icon']
                if request.FILES.get('chatbot_icon'):
                    company.chatbot_icon = request.FILES['chatbot_icon']
                company.save()

                topics = request.POST.getlist('topics')
                company.topics.set(topics)

                new_topic_name = request.POST.get('new_topic', '').strip()
                if new_topic_name:
                    new_topic, _ = Topic.objects.get_or_create(name=new_topic_name)
                    company.topics.add(new_topic)

                User.objects.create_user(
                    username=admin_username,
                    password=admin_password,
                    email=admin_email or '',
                    role=User.Role.ADMIN,
                    company=company,
                )
        except Exception as e:
            messages.error(request, f"Failed to create company: {e}")
            return render(request, 'superuser/company_form.html', {
                'global_topics': global_topics,
                'ai_models': ai_models,
                'packages': packages,
                'show_admin_fields': True,
                'is_create': True,
            })

        messages.success(
            request,
            f"Company '{company.name}' created! Admin login — Username: {admin_username} | Login at /login/"
        )
        if company.sitemap_url:
            spider_script = os.path.join(os.path.dirname(__file__), 'sitemap_spider.py')
            subprocess.Popen([sys.executable, spider_script, str(company.id), company.sitemap_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            
        return redirect('superuser_dashboard')

    return render(request, 'superuser/company_form.html', {
        'global_topics': global_topics,
        'ai_models': ai_models,
        'packages': packages,
        'show_admin_fields': True,
        'is_create': True,
    })

@superuser_required
def company_edit(request, pk):
    from .models import AIModel, SiteSettings, Package
    company = get_object_or_404(Company, pk=pk)
    global_topics = Topic.objects.all()
    ai_models = AIModel.objects.all().order_by('provider', 'display_name')
    packages = Package.objects.all().order_by('name')
    company_admins = company.users.filter(role=User.Role.ADMIN)
    site_settings = SiteSettings.objects.first()
    if request.method == 'POST':
        company.name = request.POST.get('name')
        company.chatbot_name = request.POST.get('chatbot_name')
        company.welcome_message = request.POST.get('welcome_message', company.welcome_message)
        company.ai_provider = request.POST.get('ai_provider', 'gemini')
        company.api_key = request.POST.get('api_key', '').strip() or None
        company.system_prompt = request.POST.get('system_prompt', '').strip() or None
        company.google_sheet_url = request.POST.get('google_sheet_url', '').strip() or None
        company.sitemap_url = request.POST.get('sitemap_url', '').strip() or None
        ai_model_id = request.POST.get('ai_model')
        company.allowed_domain = request.POST.get('allowed_domain', '').strip().lower() or None
        company.auto_escalate_via_sms = (request.POST.get('auto_escalate_via_sms') == 'on')
        company.auto_escalate_every_message = (request.POST.get('auto_escalate_every_message') == 'on')
        company.show_watermark = (request.POST.get('show_watermark') == 'on')
        company.image_limit = int(request.POST.get('image_limit', 50))
        if 'widget_position' in request.POST:
            company.widget_position = request.POST.get('widget_position')
        company.fb_messenger_enabled = (request.POST.get('fb_messenger_enabled') == 'on')
        company.fb_verify_token = request.POST.get('fb_verify_token', '').strip() or None
        company.fb_page_id = request.POST.get('fb_page_id', '').strip() or None
        company.fb_page_access_token = request.POST.get('fb_page_access_token', '').strip() or None
        company.fb_typing_indicator_enabled = (request.POST.get('fb_typing_indicator_enabled') == 'on')
        company.fb_mark_seen_enabled = (request.POST.get('fb_mark_seen_enabled') == 'on')
        company.agent_fb_comments_access = (request.POST.get('agent_fb_comments_access') == 'on')
        company.whatsapp_enabled = (request.POST.get('whatsapp_enabled') == 'on')
        company.wa_phone_number_id = request.POST.get('wa_phone_number_id', '').strip() or None
        company.wa_business_account_id = request.POST.get('wa_business_account_id', '').strip() or None
        company.wa_access_token = request.POST.get('wa_access_token', '').strip() or None
        
        old_sitemap_url = company.sitemap_url
        company.sitemap_url = request.POST.get('sitemap_url', '').strip() or None
        
        company.ecom_api_url = request.POST.get('ecom_api_url', '').strip() or None
        company.ecom_api_key = request.POST.get('ecom_api_key', '').strip() or None
        company.subscription_valid_until = request.POST.get('subscription_valid_until') or None
        company.message_limit = int(request.POST.get('message_limit') or 0)
        company.package_id = request.POST.get('package') or None
        
        # If ai_model is selected, use its provider and model_name
        if ai_model_id:
            selected_ai_model = get_object_or_404(AIModel, id=ai_model_id)
            company.ai_provider = selected_ai_model.provider
            company.model_name = selected_ai_model.model_name
            company.ai_model = selected_ai_model
        else:
            company.ai_model = None
            
        if request.FILES.get('icon'):
            company.icon = request.FILES['icon']
        if request.FILES.get('chatbot_icon'):
            company.chatbot_icon = request.FILES['chatbot_icon']
        company.save()

        topics = request.POST.getlist('topics')
        company.topics.set(topics)

        new_topic_name = request.POST.get('new_topic', '').strip()
        if new_topic_name:
            new_topic, _ = Topic.objects.get_or_create(name=new_topic_name)
            company.topics.add(new_topic)

        admin_username = request.POST.get('admin_username', '').strip()
        admin_password = request.POST.get('admin_password', '').strip()
        admin_email = request.POST.get('admin_email', '').strip()
        if admin_username and admin_password:
            if User.objects.filter(username=admin_username).exists():
                messages.error(request, f"Username '{admin_username}' is already taken.")
                return render(request, 'superuser/company_form.html', {
                    'object': company,
                    'global_topics': global_topics,
                    'ai_models': ai_models,
                    'packages': packages,
                    'show_admin_fields': True,
                    'company_admins': company_admins,
                    'site_settings': site_settings,
                })
            User.objects.create_user(
                username=admin_username,
                password=admin_password,
                email=admin_email or '',
                role=User.Role.ADMIN,
                company=company,
            )
            messages.success(request, f"New admin user '{admin_username}' created for '{company.name}'.")

        reset_admin_id = request.POST.get('reset_admin_id', '').strip()
        new_password = request.POST.get('new_password', '').strip()
        if reset_admin_id and new_password:
            admin_user = get_object_or_404(User, pk=reset_admin_id, company=company, role=User.Role.ADMIN)
            admin_user.set_password(new_password)
            admin_user.save()
            messages.success(request, f"Password reset for admin '{admin_user.username}'.")

        messages.success(request, f"Configuration for '{company.name}' has been updated.")
        if company.sitemap_url and company.sitemap_url != old_sitemap_url:
            spider_script = os.path.join(os.path.dirname(__file__), 'sitemap_spider.py')
            subprocess.Popen([sys.executable, spider_script, str(company.id), company.sitemap_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            
        return redirect('superuser_company_edit', pk=company.pk)

    return render(request, 'superuser/company_form.html', {
        'object': company,
        'global_topics': global_topics,
        'ai_models': ai_models,
        'packages': packages,
        'show_admin_fields': True,
        'company_admins': company_admins,
        'site_settings': site_settings,
    })

@superuser_required
def company_delete(request, pk):
    company = get_object_or_404(Company, pk=pk)
    if request.method == 'POST':
        name = company.name
        company.delete()
        messages.success(request, f"Tenant company '{name}' has been deleted.")
    return redirect('superuser_dashboard')

@superuser_required
def superuser_client_list(request):
    import datetime
    companies = Company.objects.all().order_by('-created_at')
    
    q = request.GET.get('q', '').strip()
    if q:
        companies = companies.filter(name__icontains=q)
        
    host = request.get_host()
    scheme = 'https' if request.is_secure() else 'http'
    base_url = f"{scheme}://{host}"
    
    return render(request, 'superuser/client_list.html', {
        'companies': companies,
        'base_url': base_url,
        'today': datetime.date.today(),
        'search_query': q,
    })

@superuser_required
@require_POST
def superuser_test_single_fb(request, company_id):
    import requests as req_lib
    company = get_object_or_404(Company, id=company_id)
    if not company.fb_messenger_enabled or not company.fb_page_id or not company.fb_page_access_token:
        return JsonResponse({'status': 'error', 'message': 'FB integration not configured.'})
    
    url = f"https://graph.facebook.com/v17.0/{company.fb_page_id}?fields=id,name&access_token={company.fb_page_access_token}"
    try:
        response = req_lib.get(url, timeout=10)
        res_data = response.json()
        if response.status_code == 200:
            verified_id = res_data.get('id')
            verified_name = res_data.get('name', '(unknown)')
            if verified_id == company.fb_page_id:
                return JsonResponse({'status': 'success', 'message': f"Connected to '{verified_name}'"})
            else:
                return JsonResponse({'status': 'warning', 'message': f"ID mismatch. Got {verified_id}"})
        else:
            err = res_data.get('error', {})
            return JsonResponse({'status': 'error', 'message': err.get('message', 'API Error')})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)})

@superuser_required
@require_POST
def superuser_test_all_fb(request):
    import requests as req_lib
    import concurrent.futures
    
    companies = Company.objects.filter(fb_messenger_enabled=True).exclude(fb_page_id='').exclude(fb_page_access_token='')
    results = []
    
    def test_company(company):
        url = f"https://graph.facebook.com/v17.0/{company.fb_page_id}?fields=id,name&access_token={company.fb_page_access_token}"
        try:
            response = req_lib.get(url, timeout=10)
            if response.status_code == 200:
                res_data = response.json()
                verified_id = res_data.get('id')
                if verified_id == company.fb_page_id:
                    return {'id': company.id, 'name': company.name, 'status': 'success', 'message': f"Connected to '{res_data.get('name')}'"}
                else:
                    return {'id': company.id, 'name': company.name, 'status': 'error', 'message': 'ID mismatch'}
            else:
                err = response.json().get('error', {})
                return {'id': company.id, 'name': company.name, 'status': 'error', 'message': err.get('message', 'API Error')}
        except Exception as e:
            return {'id': company.id, 'name': company.name, 'status': 'error', 'message': str(e)}

    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        results = list(executor.map(test_company, companies))
        
    return JsonResponse({'results': results})

@superuser_required
def company_test(request, pk):
    company = get_object_or_404(Company, pk=pk)
    return render(request, 'superuser/company_test.html', {'company': company})

@superuser_required
@require_POST
def company_chat_api(request, pk):
    company = get_object_or_404(Company, pk=pk)
    try:
        data = json.loads(request.body)
        user_message = data.get('message', '').strip()
        if not user_message:
            return JsonResponse({'status': 'error', 'error': 'Empty message.'}, status=400)
            
        system_prompt = get_effective_prompt(company, locals().get('user_message') or locals().get('message_text'))
        
        if company.ai_provider == 'gemini':
            bot_response = get_gemini_response(
                api_key=company.api_key,
                model_name=company.model_name,
                system_prompt=system_prompt,
                prompt_text=user_message,
                google_sheet_url=company.google_sheet_url
            )
        elif company.ai_provider == 'openai':
            bot_response = get_openai_response(
                api_key=company.api_key,
                model_name=company.model_name,
                system_prompt=system_prompt,
                prompt_text=user_message,
                google_sheet_url=company.google_sheet_url
            )
        else:
            return JsonResponse({'status': 'error', 'error': f'Unsupported provider: {company.ai_provider}'}, status=400)
            
        return JsonResponse({'status': 'success', 'response': bot_response})
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)

# Superuser global topic management
@superuser_required
def superuser_topic_list(request):
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        if name:
            topic, created = Topic.objects.get_or_create(name=name)
            if created:
                messages.success(request, f"Topic scope '{name}' added globally.")
            else:
                messages.warning(request, f"Topic scope '{name}' already exists.")
        return redirect('superuser_topic_list')
        
    topics = Topic.objects.all().order_by('name')
    return render(request, 'superuser/topic_list.html', {'topics': topics})

@superuser_required
def superuser_topic_delete(request, pk):
    topic = get_object_or_404(Topic, pk=pk)
    if request.method == 'POST':
        name = topic.name
        topic.delete()
        messages.success(request, f"Topic '{name}' has been deleted globally.")
    return redirect('superuser_topic_list')

# ----------------- CLIENT ADMIN CONSOLE VIEWS -----------------

# Helper to retrieve active company context for request user
def get_admin_company(request):
    if request.user.is_superuser:
        # Fallback to first company for superuser previewing admin console
        return Company.objects.first()
    return request.user.company

@admin_required
def admin_dashboard(request):
    company = get_admin_company(request)
    if not company:
        messages.error(request, "No company associated with your admin account.")
        return redirect('home')
        
    faq_count = company.faqs.count()
    custom_param_count = company.custom_parameters.count()
    topic_count = company.topics.count()
    quicktype_count = company.quick_types.count()
    
    return render(request, 'admin/dashboard.html', {
        'faq_count': faq_count,
        'custom_param_count': custom_param_count,
        'topic_count': topic_count,
        'quicktype_count': quicktype_count,
        'company': company,
    })

@admin_required
def admin_company_settings(request):
    """Admin can edit company settings like welcome message"""
    company = get_admin_company(request)
    if not company:
        messages.error(request, "No company associated with your admin account.")
        return redirect('home')
    
    if request.method == 'POST':
        company.chatbot_name = request.POST.get('chatbot_name', company.chatbot_name)
        company.welcome_message = request.POST.get('welcome_message', company.welcome_message)
        company.show_watermark = (request.POST.get('show_watermark') == 'on')
        
        company.save()
        messages.success(request, "Company settings updated successfully.")
        
        return redirect('admin_dashboard')
    
    return render(request, 'admin/company_settings.html', {'company': company})

@admin_required
@require_POST
def admin_toggle_ai_agent(request):
    company = get_admin_company(request)
    if not company:
        return JsonResponse({'status': 'error', 'error': 'No company associated with your admin account.'}, status=400)
    company.ai_agent_active = not company.ai_agent_active
    company.save(update_fields=['ai_agent_active'])
    return JsonResponse({'status': 'success', 'ai_agent_active': company.ai_agent_active})

@login_required
def update_password_view(request):
    if request.method == 'POST':
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')

        if not old_password or not new_password or not confirm_password:
            messages.error(request, "All fields are required.")
        elif new_password != confirm_password:
            messages.error(request, "New passwords do not match.")
        elif not request.user.check_password(old_password):
            messages.error(request, "Incorrect old password.")
        else:
            request.user.set_password(new_password)
            request.user.save()
            from django.contrib.auth import update_session_auth_hash
            update_session_auth_hash(request, request.user)
            messages.success(request, "Your password has been successfully updated.")
            
            if request.user.role == User.Role.SUPERUSER or request.user.is_superuser:
                return redirect('superuser_dashboard')
            elif request.user.role == User.Role.ADMIN:
                return redirect('admin_dashboard')
            else:
                return redirect('agent_dashboard')
                
    if request.user.role == User.Role.SUPERUSER or request.user.is_superuser:
        base_template = 'superuser/base_superuser.html'
        active_block = 'superuser_update_password_active'
    elif request.user.role == User.Role.ADMIN:
        base_template = 'admin/base_admin.html'
        active_block = 'admin_update_password_active'
    else:
        base_template = 'agent/base_agent.html'
        active_block = 'agent_update_password_active'
        
    return render(request, 'pass/update_password.html', {
        'base_template': base_template,
        'active_block': active_block
    })

@admin_required
def admin_faq_list(request):
    company = get_admin_company(request)
    faqs = company.faqs.all()
    return render(request, 'admin/faq_list.html', {'faqs': faqs})

@admin_required
def admin_faq_create(request):
    company = get_admin_company(request)
    if request.method == 'POST':
        question = request.POST.get('question')
        answer = request.POST.get('answer')
        FAQ.objects.create(company=company, question=question, answer=answer)
        messages.success(request, "Quick Response FAQ added successfully.")
        return redirect('admin_faq_list')
    return render(request, 'admin/faq_form.html')

@admin_required
def admin_faq_edit(request, pk):
    company = get_admin_company(request)
    faq = get_object_or_404(FAQ, pk=pk, company=company)
    if request.method == 'POST':
        faq.question = request.POST.get('question')
        faq.answer = request.POST.get('answer')
        faq.save()
        messages.success(request, "FAQ Quick Response updated successfully.")
        return redirect('admin_faq_list')
    return render(request, 'admin/faq_form.html', {'object': faq})

@admin_required
def admin_faq_delete(request, pk):
    company = get_admin_company(request)
    faq = get_object_or_404(FAQ, pk=pk, company=company)
    if request.method == 'POST':
        faq.delete()
        messages.success(request, "FAQ Quick Response has been deleted.")
    return redirect('admin_faq_list')

# Custom Parameters Views
@admin_required
def admin_custom_parameter_list(request):
    company = get_admin_company(request)
    parameters = company.custom_parameters.all().order_by('-created_at')
    return render(request, 'admin/custom_parameter_list.html', {'parameters': parameters})

@admin_required
def admin_custom_parameter_create(request):
    company = get_admin_company(request)
    if request.method == 'POST':
        keyword = request.POST.get('keyword', '').strip()
        response = request.POST.get('response', '').strip()
        if keyword and response:
            CustomParameter.objects.create(company=company, keyword=keyword, response=response)
            messages.success(request, "Database entry added successfully.")
        return redirect('admin_custom_parameter_list')
    return render(request, 'admin/custom_parameter_form.html')

@admin_required
def admin_custom_parameter_edit(request, pk):
    company = get_admin_company(request)
    parameter = get_object_or_404(CustomParameter, pk=pk, company=company)
    if request.method == 'POST':
        parameter.keyword = request.POST.get('keyword', '').strip()
        parameter.response = request.POST.get('response', '').strip()
        parameter.save()
        messages.success(request, "Database entry updated successfully.")
        return redirect('admin_custom_parameter_list')
    return render(request, 'admin/custom_parameter_form.html', {'object': parameter})

@admin_required
def admin_custom_parameter_delete(request, pk):
    company = get_admin_company(request)
    parameter = get_object_or_404(CustomParameter, pk=pk, company=company)
    if request.method == 'POST':
        parameter.delete()
        messages.success(request, "Database entry has been deleted.")
    return redirect('admin_custom_parameter_list')


# QuickType admin CRUD for company admins
@admin_required
def admin_quicktype_list(request):
    company = get_admin_company(request)
    quicktypes = company.quick_types.all().order_by('-created_at')
    return render(request, 'admin/quick_type_list.html', {'quicktypes': quicktypes})


@admin_required
def admin_quicktype_create(request):
    company = get_admin_company(request)
    if request.method == 'POST':
        label = request.POST.get('label', '').strip()
        content = request.POST.get('content', '').strip() or label
        if label:
            from .models import QuickType
            QuickType.objects.create(company=company, label=label, content=content)
            messages.success(request, 'Quick Type added successfully.')
        return redirect('admin_quicktype_list')
    return render(request, 'admin/quick_type_form.html')


@admin_required
def admin_quicktype_edit(request, pk):
    company = get_admin_company(request)
    from .models import QuickType
    qt = get_object_or_404(QuickType, pk=pk, company=company)
    if request.method == 'POST':
        qt.label = request.POST.get('label', '').strip()
        qt.content = request.POST.get('content', '').strip() or qt.label
        qt.save()
        messages.success(request, 'Quick Type updated successfully.')
        return redirect('admin_quicktype_list')
    return render(request, 'admin/quick_type_form.html', {'object': qt})


@admin_required
def admin_quicktype_delete(request, pk):
    company = get_admin_company(request)
    from .models import QuickType
    qt = get_object_or_404(QuickType, pk=pk, company=company)
    if request.method == 'POST':
        qt.delete()
        messages.success(request, 'Quick Type deleted.')
    return redirect('admin_quicktype_list')

@admin_required
def admin_widget_view(request):
    company = get_admin_company(request)
    host = request.get_host()
    scheme = 'https' if request.is_secure() else 'http'
    base_url = f"{scheme}://{host}"

    if request.method == 'POST':
        company.widget_position = request.POST.get('widget_position', 'right')
        company.widget_bottom_offset = max(0, min(200, int(request.POST.get('widget_bottom_offset', 24) or 24)))
        company.widget_side_offset = max(0, min(200, int(request.POST.get('widget_side_offset', 24) or 24)))
        company.widget_button_size = max(40, min(100, int(request.POST.get('widget_button_size', 60) or 60)))
        company.widget_panel_width = max(280, min(600, int(request.POST.get('widget_panel_width', 360) or 360)))
        company.widget_panel_height = max(300, min(800, int(request.POST.get('widget_panel_height', 480) or 480)))
        company.widget_sound_enabled = request.POST.get('widget_sound_enabled') == 'on'
        # collect enabled positions from checkboxes
        enabled = []
        if request.POST.get('pos_bottom_right'):
            enabled.append('bottom-right')
        if request.POST.get('pos_bottom_left'):
            enabled.append('bottom-left')
        if request.POST.get('pos_left_center'):
            enabled.append('left-center')
        if request.POST.get('pos_right_center'):
            enabled.append('right-center')
        # fallback to single position if none selected
        company.widget_positions = ','.join(enabled) if enabled else company.widget_position
        company.save()
        messages.success(request, "Widget appearance settings saved.")
        return redirect('admin_widget_view')

    return render(request, 'admin/widget_detail.html', {
        'company': company,
        'base_url': base_url,
        'widget_attrs': get_widget_embed_attrs(company, request),
        'enabled_positions': (company.widget_positions or company.widget_position).split(',') if (company.widget_positions or company.widget_position) else [],
    })

@admin_required
def admin_widget_preview(request):
    company = get_admin_company(request)
    host = request.get_host()
    scheme = 'https' if request.is_secure() else 'http'
    base_url = f"{scheme}://{host}"
    return render(request, 'chatbot/preview.html', {
        'company': company,
        'base_url': base_url,
        'widget_attrs': get_widget_embed_attrs(company, request),
    })

@admin_required
def admin_agent_list(request):
    company = get_admin_company(request)
    agents = company.users.filter(role=User.Role.AGENT).order_by('username')
    return render(request, 'admin/agent_list.html', {'agents': agents})

@admin_required
def admin_agent_create(request):
    company = get_admin_company(request)
    if request.method == 'POST':
        username = request.POST.get('username', '').strip()
        password = request.POST.get('password', '').strip()
        email = request.POST.get('email', '').strip()
        agent_name = request.POST.get('agent_name', '').strip()
        if not username or not password:
            messages.error(request, "Agent username and password are required.")
            return render(request, 'admin/agent_form.html')

        if User.objects.filter(username=username).exists():
            messages.error(request, f"Username '{username}' is already taken.")
            return render(request, 'admin/agent_form.html')

        # create user without photo first
        user = User.objects.create_user(
            username=username,
            password=password,
            email=email or '',
            agent_name=agent_name or username,
            role=User.Role.AGENT,
            company=company,
        )

        # phone_number removed — no SMS delivery in this deployment

        # handle optional agent photo upload and compress it to small size
        if request.FILES.get('agent_photo'):
            compressed = _compress_image_file(request.FILES.get('agent_photo'), target_kb=15)
            if compressed:
                try:
                    user.agent_photo.save(compressed.name, compressed)
                except Exception:
                    pass

        messages.success(request, f"Agent '{username}' created successfully.")
        return redirect('admin_agent_list')

    return render(request, 'admin/agent_form.html')

@admin_required
def admin_agent_edit(request, pk):
    company = get_admin_company(request)
    agent = get_object_or_404(User, pk=pk, company=company, role=User.Role.AGENT)
    
    if request.method == 'POST':
        password = request.POST.get('password', '').strip()
        email = request.POST.get('email', '').strip()
        agent_name = request.POST.get('agent_name', '').strip()
        
        agent.email = email or ''
        agent.agent_name = agent_name or agent.username
        
        if password:
            agent.set_password(password)
            
        if request.FILES.get('agent_photo'):
            compressed = _compress_image_file(request.FILES.get('agent_photo'), target_kb=15)
            if compressed:
                try:
                    agent.agent_photo.save(compressed.name, compressed)
                except Exception:
                    pass
                    
        agent.save()
        messages.success(request, f"Agent '{agent.username}' updated successfully.")
        return redirect('admin_agent_list')
        
    return render(request, 'admin/agent_form.html', {'object': agent})


@admin_required
def admin_agent_delete(request, pk):
    company = get_admin_company(request)
    agent = get_object_or_404(User, pk=pk, company=company, role=User.Role.AGENT)
    if request.method == 'POST':
        username = agent.username
        agent.delete()
        messages.success(request, f"Agent '{username}' has been removed.")
    return redirect('admin_agent_list')

# Media Management Views
@admin_required
def admin_media_list(request):
    company = get_admin_company(request)
    from .models import CompanyMedia
    media_items = CompanyMedia.objects.filter(company=company).order_by('-created_at')
    return render(request, 'admin/media_list.html', {'media_items': media_items})


@admin_required
def admin_media_create(request):
    company = get_admin_company(request)
    from .models import CompanyMedia, CompanyMediaFile
    if request.method == 'POST':
        tags = request.POST.get('tags', '').strip()
        images = request.FILES.getlist('images')
        if not images and request.FILES.get('image'):
            images = [request.FILES.get('image')]
            
        if images and tags:
            total_existing = CompanyMedia.objects.filter(company=company).count() + CompanyMediaFile.objects.filter(company_media__company=company).count()
            if company.image_limit > 0 and (total_existing + len(images)) > company.image_limit:
                messages.error(request, f"Upload denied. Adding {len(images)} images exceeds your company's limit of {company.image_limit}.")
                return render(request, 'admin/media_form.html')
                
            media_item = CompanyMedia.objects.create(company=company, image=images[0], tags=tags)
            if len(images) > 1:
                for img in images[1:]:
                    CompanyMediaFile.objects.create(company_media=media_item, image=img)
            messages.success(request, f'{len(images)} media item(s) added successfully.')
            return redirect('admin_media_list')
        else:
            messages.error(request, 'Please provide at least one image and tags.')
    return render(request, 'admin/media_form.html')


@admin_required
def admin_media_edit(request, pk):
    company = get_admin_company(request)
    from .models import CompanyMedia, CompanyMediaFile
    media_item = get_object_or_404(CompanyMedia, pk=pk, company=company)
    if request.method == 'POST':
        tags = request.POST.get('tags', '').strip()
        images = request.FILES.getlist('images')
        if not images and request.FILES.get('image'):
            images = [request.FILES.get('image')]
            
        if tags:
            media_item.tags = tags
            media_item.save()
            if images:
                total_existing = CompanyMedia.objects.filter(company=company).count() + CompanyMediaFile.objects.filter(company_media__company=company).count()
                if company.image_limit > 0 and (total_existing + len(images)) > company.image_limit:
                    messages.error(request, f"Upload denied. Adding {len(images)} images exceeds your company's limit of {company.image_limit}.")
                    return render(request, 'admin/media_form.html', {'media_item': media_item})
                
                # Append new images to the group
                for img in images:
                    CompanyMediaFile.objects.create(company_media=media_item, image=img)
                messages.success(request, f'{len(images)} additional media item(s) added successfully.')
            else:
                messages.success(request, 'Media item updated successfully.')
            return redirect('admin_media_list')
        else:
            messages.error(request, 'Tags cannot be empty.')
    return render(request, 'admin/media_form.html', {'media_item': media_item})


@admin_required
def admin_media_delete(request, pk):
    company = get_admin_company(request)
    from .models import CompanyMedia
    media_item = get_object_or_404(CompanyMedia, pk=pk, company=company)
    if request.method == 'POST':
        media_item.delete()
        messages.success(request, 'Media item deleted.')
    return redirect('admin_media_list')


# Custom Forms builder and submissions views
@admin_required
def admin_form_list(request):
    company = get_admin_company(request)
    from .models import Form
    forms = Form.objects.filter(company=company).order_by('-created_at')
    return render(request, 'admin/form_list.html', {'forms': forms})


@admin_required
def admin_form_create(request):
    company = get_admin_company(request)
    if request.method == 'POST':
        title = request.POST.get('title', '').strip()
        keyword = request.POST.get('keyword', '').strip()
        is_shared = (request.POST.get('is_shared') == 'on')
        fields = request.POST.getlist('fields')
        
        if title and keyword:
            from .models import Form, FormField
            with transaction.atomic():
                form = Form.objects.create(company=company, title=title, keyword=keyword, is_shared=is_shared)
                for f_name in fields:
                    name_stripped = f_name.strip()
                    if name_stripped:
                        FormField.objects.create(form=form, name=name_stripped)
            messages.success(request, f"Custom form '{title}' created successfully.")
            return redirect('admin_form_list')
    return render(request, 'admin/form_form.html')


@admin_required
def admin_form_edit(request, pk):
    company = get_admin_company(request)
    from .models import Form, FormField
    form = get_object_or_404(Form, pk=pk, company=company)
    
    if request.method == 'POST':
        title = request.POST.get('title', '').strip()
        keyword = request.POST.get('keyword', '').strip()
        is_shared = (request.POST.get('is_shared') == 'on')
        fields = request.POST.getlist('fields')
        
        if title and keyword:
            with transaction.atomic():
                form.title = title
                form.keyword = keyword
                form.is_shared = is_shared
                form.save()
                
                # Simple recreation of fields for editing
                form.fields.all().delete()
                for f_name in fields:
                    name_stripped = f_name.strip()
                    if name_stripped:
                        FormField.objects.create(form=form, name=name_stripped)
            messages.success(request, f"Custom form '{title}' updated successfully.")
            return redirect('admin_form_list')
            
    return render(request, 'admin/form_form.html', {'object': form})


@admin_required
def admin_form_delete(request, pk):
    company = get_admin_company(request)
    from .models import Form
    form = get_object_or_404(Form, pk=pk, company=company)
    if request.method == 'POST':
        title = form.title
        form.delete()
        messages.success(request, f"Custom form '{title}' has been deleted.")
    return redirect('admin_form_list')


@admin_required
def admin_submission_list(request):
    company = get_admin_company(request)
    from .models import FormSubmission, Form
    
    forms = Form.objects.filter(company=company).order_by('title')
    submissions = FormSubmission.objects.filter(form__company=company).order_by('-created_at')
    
    selected_form_id = request.GET.get('form_id')
    if selected_form_id:
        submissions = submissions.filter(form_id=selected_form_id)
        
    return render(request, 'admin/submission_list.html', {
        'submissions': submissions,
        'forms': forms,
        'selected_form_id': selected_form_id
    })


@admin_required
def admin_submission_detail(request, pk):
    company = get_admin_company(request)
    from .models import FormSubmission
    submission = get_object_or_404(FormSubmission, pk=pk, form__company=company)
    status_choices = FormSubmission.Status.choices
    return render(request, 'admin/submission_detail.html', {'object': submission, 'status_choices': status_choices})


@admin_required
@require_POST
def admin_submission_update_status(request, pk):
    company = get_admin_company(request)
    from .models import FormSubmission
    submission = get_object_or_404(FormSubmission, pk=pk, form__company=company)
    status = request.POST.get('status')
    if status and status in [choice[0] for choice in FormSubmission.Status.choices]:
        submission.status = status
        submission.save()
        messages.success(request, f"Submission status updated to '{submission.get_status_display()}'.")
    else:
        messages.error(request, "Invalid status choice.")
    return redirect('admin_submission_detail', pk=pk)


# Agent submissions views
@agent_required
def agent_submission_list(request):
    company = request.user.company
    from .models import FormSubmission, Form
    
    forms = Form.objects.filter(company=company, is_shared=True).order_by('title')
    submissions = FormSubmission.objects.filter(form__company=company, form__is_shared=True).order_by('-created_at')
    
    selected_form_id = request.GET.get('form_id')
    if selected_form_id:
        submissions = submissions.filter(form_id=selected_form_id)
        
    return render(request, 'agent/submission_list.html', {
        'submissions': submissions,
        'forms': forms,
        'selected_form_id': selected_form_id
    })


@agent_required
def agent_submission_detail(request, pk):
    company = request.user.company
    from .models import FormSubmission
    submission = get_object_or_404(FormSubmission, pk=pk, form__company=company, form__is_shared=True)
    status_choices = FormSubmission.Status.choices
    return render(request, 'agent/submission_detail.html', {'object': submission, 'status_choices': status_choices})


@agent_required
@require_POST
def agent_submission_update_status(request, pk):
    company = request.user.company
    from .models import FormSubmission
    submission = get_object_or_404(FormSubmission, pk=pk, form__company=company, form__is_shared=True)
    status = request.POST.get('status')
    if status and status in [choice[0] for choice in FormSubmission.Status.choices]:
        submission.status = status
        submission.save()
        messages.success(request, f"Submission status updated to '{submission.get_status_display()}'.")
    else:
        messages.error(request, "Invalid status choice.")
    return redirect('agent_submission_detail', pk=pk)


# ----------------- AGENT DESK VIEWS -----------------

@agent_required
def agent_dashboard(request):
    company = request.user.company
    faq_count = company.faqs.count()
    agent_count = company.users.filter(role=User.Role.AGENT).count()
    # Escalations claimed by this agent, grouped by visitor
    claimed = list(company.escalations.filter(claimed_by=request.user).order_by('-updated_at'))
    pending = list(company.escalations.filter(claimed_by__isnull=True).order_by('-updated_at'))
    live_count = len(set(e.visitor_id for e in claimed if e.visitor_id))
    host = request.get_host()
    scheme = 'https' if request.is_secure() else 'http'
    base_url = f"{scheme}://{host}"

    # ── Resolve Facebook visitor names for escalations using cache ──────────
    for esc in claimed + pending:
        if esc.visitor_id and esc.visitor_id.startswith('fb_'):
            prof = get_visitor_profile(esc.visitor_id, company)
            esc.fb_name = prof['name']
        else:
            esc.fb_name = None
        esc.fb_pic = None
    # ────────────────────────────────────────────────────────────────────────

    return render(request, 'agent/dashboard.html', {
        'company': company,
        'faq_count': faq_count,
        'agent_count': agent_count,
        'claimed': claimed,
        'pending': pending,
        'live_count': live_count,
        'base_url': base_url,
    })



@agent_required
def agent_complaints(request):
    """Simple CRUD-ish view for store complaints accessible to agents."""
    company = request.user.company
    if not company:
        messages.error(request, 'No company associated with your account.')
        return redirect('agent_dashboard')

    if request.method == 'POST':
        # create new complaint
        title = request.POST.get('title', '').strip()
        description = request.POST.get('description', '').strip()
        customer_name = request.POST.get('customer_name', '').strip()
        if title:
            from .models import StoreComplaint
            comp = StoreComplaint.objects.create(
                company=company,
                title=title,
                description=description,
                customer_name=customer_name or None,
                created_by=request.user,
            )
            messages.success(request, 'Complaint created.')
            return redirect('agent_complaints')
        else:
            messages.error(request, 'Title is required to create a complaint.')

    # list complaints for this company
    from .models import StoreComplaint
    complaints = company.store_complaints.all().order_by('-created_at')
    status_choices = StoreComplaint.Status.choices
    return render(request, 'agent/complaints.html', {'company': company, 'complaints': complaints, 'status_choices': status_choices})


@agent_required
@require_POST
def agent_update_complaint_status(request, pk):
    from .models import StoreComplaint
    company = request.user.company
    comp = get_object_or_404(StoreComplaint, pk=pk, company=company)
    status = request.POST.get('status')
    if status and status in [s[0] for s in StoreComplaint.Status.choices]:
        comp.status = status
        comp.updated_by = request.user
        comp.save()
        messages.success(request, 'Complaint status updated.')
    else:
        messages.error(request, 'Invalid status.')
    return redirect('agent_complaints')


@agent_required
@require_POST
def agent_claim_escalation(request, pk):
    """Agent claims a pending escalation and is taken to the chat view."""
    company = request.user.company
    esc = get_object_or_404(Escalation, pk=pk, company=company)
    if esc.claimed_by and esc.claimed_by != request.user:
        messages.error(request, 'This escalation is already claimed by another agent.')
        return redirect('agent_dashboard')
    esc.claimed_by = request.user
    esc.claimed_at = timezone.now()
    esc.is_handled = True
    esc.save()
    # system message: agent joined
    try:
        ChatMessage.objects.create(
            company=company,
            sender='system',
            content=f"Agent {request.user.agent_name or request.user.username} joined.",
            visitor_id=esc.visitor_id,
            escalation=esc,
        )
    except Exception:
        pass
    
    return redirect('agent_chat', visitor_id=esc.visitor_id)


@agent_or_admin_required
def agent_chat(request, visitor_id):
    """Chat panel for a specific visitor that this agent has claimed."""
    company = request.user.company
    # Require that the agent has claimed at least one escalation for this visitor
    claim = company.escalations.filter(visitor_id=visitor_id, claimed_by=request.user).order_by('-created_at').first()
    if not claim:
        messages.error(request, 'You have not claimed this conversation.')
        return redirect('agent_dashboard')
    msgs = company.messages.filter(visitor_id=visitor_id).order_by('id')
    # Compute last message id server-side to avoid negative-indexing in templates
    last_msg = msgs.last()
    last_id = last_msg.id if last_msg else 0
    quick_types = company.quick_types.all()

    visitor_name = visitor_id
    visitor_profile_pic = None
    if visitor_id.startswith('fb_') and company.fb_page_access_token:
        psid = visitor_id.replace('fb_', '')
        try:
            import requests as req_lib
            url = f"https://graph.facebook.com/{psid}?fields=first_name,last_name,profile_pic&access_token={company.fb_page_access_token}"
            resp = req_lib.get(url, timeout=3)
            if resp.status_code == 200:
                data = resp.json()
                first_name = data.get('first_name', '')
                last_name = data.get('last_name', '')
                if first_name or last_name:
                    visitor_name = f"{first_name} {last_name}".strip()
                visitor_profile_pic = data.get('profile_pic')
        except Exception:
            pass

    return render(request, 'agent/chat.html', {
        'company': company,
        'visitor_id': visitor_id,
        'visitor_name': visitor_name,
        'visitor_profile_pic': visitor_profile_pic,
        'messages': msgs,
        'escalation': claim,
        'last_id': last_id,
        'quick_types': quick_types,
    })


@agent_or_admin_required
@require_POST
def agent_send_message(request, visitor_id):
    """Agent posts a message to a visitor they have claimed."""
    company = request.user.company
    claim = company.escalations.filter(visitor_id=visitor_id, claimed_by=request.user).order_by('-created_at').first()
    if not claim:
        return JsonResponse({'status': 'error', 'error': 'You have not claimed this conversation.'}, status=403)
    
    message = (request.POST.get('message') or '').strip()
    attachments = request.FILES.getlist('attachments')
    
    for att in attachments:
        if att.size > 2 * 1024 * 1024:
            return JsonResponse({'status': 'error', 'error': f'File {att.name} exceeds the 2MB limit.'}, status=400)
    
    if not message and not attachments:
        return JsonResponse({'status': 'error', 'error': 'Empty message.'}, status=400)
    
    created_messages = []
    
    # 1. Text message first
    if message:
        msg = ChatMessage.objects.create(
            company=company,
            sender='agent',
            content=message,
            visitor_id=visitor_id,
            escalation=claim,
            message_type='text',
        )
        created_messages.append(msg)
        
    # 2. Attachments
    for att in attachments:
        ctype = att.content_type
        msg_type = 'image' if ctype.startswith('image/') else ('voice' if ctype.startswith('audio/') else 'file')
        msg = ChatMessage.objects.create(
            company=company,
            sender='agent',
            content=f'[{msg_type.upper()}]',
            visitor_id=visitor_id,
            escalation=claim,
            message_type=msg_type,
            attachment=att,
        )
        created_messages.append(msg)
    
    # Send to Meta API immediately if visitor is on a social channel
    if visitor_id and (visitor_id.startswith('fb_') or visitor_id.startswith('wa_')):
        from .utils.social_sender import send_outgoing_social_message
        for m in created_messages:
            threading.Thread(
                target=send_outgoing_social_message,
                args=(m.id,)
            ).start()
            
    # Format date for response
    response_msgs = []
    for m in created_messages:
        date_str = m.created_at.isoformat()
        if date_str.endswith('+00:00'):
            date_str = date_str[:-6] + 'Z'
        response_msgs.append({
            'id': m.id,
            'sender': m.sender,
            'content': m.content,
            'message_type': m.message_type,
            'attachment': m.attachment.url if m.attachment else None,
            'created_at': date_str,
        })
        
    return JsonResponse({'status': 'success', 'messages': response_msgs})


@agent_or_admin_required
@require_POST
def agent_release_escalation(request, visitor_id):
    """Agent releases/unclaims the escalation so AI can resume for the visitor."""
    company = request.user.company
    claim = company.escalations.filter(visitor_id=visitor_id, claimed_by=request.user).order_by('-created_at').first()
    if not claim:
        messages.error(request, 'You have not claimed this conversation.')
        return redirect('agent_dashboard')
    # Unclaim the escalation
    claim.claimed_by = None
    claim.claimed_at = None
    claim.is_handled = False
    claim.save()
    try:
        ChatMessage.objects.create(
            company=company,
            sender='system',
            content=f"Agent {request.user.agent_name or request.user.username} left.",
            visitor_id=visitor_id,
            escalation=claim,
        )
    except Exception:
        pass
    messages.success(request, 'Conversation released — AI mode will resume for this visitor.')
    return redirect('agent_dashboard')


@agent_or_admin_required
def agent_poll_messages(request, visitor_id):
    """Long-poll endpoint for the agent chat panel: returns new messages after since_id."""
    company = request.user.company
    claim = company.escalations.filter(visitor_id=visitor_id, claimed_by=request.user).order_by('-created_at').first()
    if not claim:
        return JsonResponse({'status': 'error', 'error': 'Not your conversation.'}, status=403)
    since_id = int(request.GET.get('since_id') or 0)
    msgs = company.messages.filter(visitor_id=visitor_id, id__gt=since_id).order_by('id')
    out = []
    for m in msgs:
        msg_data = {
            'id': m.id,
            'sender': m.sender,
            'content': m.content,
            'message_type': m.message_type,
            'created_at': m.created_at.isoformat(),
        }
        if m.attachment:
            msg_data['attachment'] = m.attachment.url
        elif m.attachment_url:
            msg_data['attachment'] = m.attachment_url
        out.append(msg_data)
    return JsonResponse({'status': 'success', 'messages': out})

@agent_or_admin_required
@require_POST
def agent_edit_message(request, message_id):
    company = request.user.company
    msg = get_object_or_404(ChatMessage, id=message_id, company=company, sender='agent')
    content = (request.POST.get('content') or '').strip()
    if not content:
        return JsonResponse({'status': 'error', 'error': 'Message content cannot be empty.'}, status=400)
    
    msg.content = content
    msg.save(update_fields=['content'])
    return JsonResponse({
        'status': 'success',
        'message': {
            'id': msg.id,
            'content': msg.content,
        }
    })

@agent_or_admin_required
@require_POST
def agent_delete_message(request, message_id):
    company = request.user.company
    msg = get_object_or_404(ChatMessage, id=message_id, company=company, sender='agent')
    msg_id = msg.id
    msg.delete()
    return JsonResponse({
        'status': 'success',
        'message_id': msg_id
    })

# ----------------- VISITOR WIDGET INTEGRATION -----------------

# Dynamic JS widget loader endpoint
def widget_js_view(request):
    return render(request, 'chatbot/widget.js', content_type='application/javascript')

# Helper to validate request origin domain against company's allowed_domain
def _is_domain_allowed(request, company):
    if not company.allowed_domain:
        return True
    origin = request.META.get('HTTP_ORIGIN', '') or request.META.get('HTTP_REFERER', '')
    if not origin:
        return True
    origin_lower = origin.lower()
    clean_domain = company.allowed_domain.strip().lower().replace('https://', '').replace('http://', '').split('/')[0]
    if clean_domain in origin_lower:
        return True
    # Allow same-server preview (admin widget page, local testing)
    server_host = request.get_host().split(':')[0].lower()
    return server_host in origin_lower


def _is_chat_api_allowed(request, company):
    """Chat POST runs inside the iframe; Referer is the iframe URL, not the parent site."""
    if not company.allowed_domain:
        return True
    referer = request.META.get('HTTP_REFERER', '')
    if f'/chatbot/iframe/{company.id}/' in referer:
        return True
    return _is_domain_allowed(request, company)

# Clean iframe chat container
@xframe_options_exempt
@csrf_exempt
@xframe_options_exempt
def chatbot_iframe_view(request, company_id):
    from .models import Theme
    company = get_object_or_404(Company, id=company_id)
    if not _is_domain_allowed(request, company):
        return JsonResponse({'error': 'Unauthorized domain. This chatbot widget is not authorized for this website.'}, status=403)
    faqs = company.faqs.all()
    quick_types = company.quick_types.all()
    theme, _ = Theme.objects.get_or_create(company=company)
    return render(request, 'chatbot/iframe.html', {
        'company': company,
        'faqs': faqs,
        'quick_types': quick_types,
        'theme': theme,
        'sound_enabled': company.widget_sound_enabled,
        'welcome_message': company.welcome_message,
    })


@csrf_exempt
@xframe_options_exempt
def chatbot_fullscreen_view(request, company_id):
    """Fullscreen chat view for mobile/desktop."""
    from .models import Theme
    company = get_object_or_404(Company, id=company_id)
    # No domain check for fullscreen - it's opened from the widget itself
    faqs = company.faqs.all()
    quick_types = company.quick_types.all()
    theme, _ = Theme.objects.get_or_create(company=company)
    return render(request, 'chatbot/iframe.html', {
        'company': company,
        'faqs': faqs,
        'quick_types': quick_types,
        'theme': theme,
        'sound_enabled': getattr(company, 'widget_sound_enabled', False),
        'welcome_message': company.welcome_message,
        'is_fullscreen': True,
    })


@csrf_exempt
def chatbot_get_theme(request, company_id):
    """API endpoint to fetch current theme colors for auto-update."""
    from .models import Theme
    company = get_object_or_404(Company, id=company_id)
    theme, _ = Theme.objects.get_or_create(company=company)
    return JsonResponse({
        'status': 'success',
        'theme': {
            'primary_color': theme.primary_color,
            'secondary_color': theme.secondary_color,
            'background_color': theme.background_color,
            'text_primary': theme.text_primary,
            'text_muted': theme.text_muted,
            'border_color': theme.border_color,
            'bot_bubble_bg': theme.bot_bubble_bg,
            'user_bubble_bg': theme.user_bubble_bg,
            'send_button_bg': theme.send_button_bg,
        }
    })

@csrf_exempt
@xframe_options_exempt
@require_POST
def chatbot_iframe_set_agent(request, company_id):
    """Set active agent name for the chat interface"""
    company = get_object_or_404(Company, id=company_id)
    if not _is_domain_allowed(request, company):
        return JsonResponse({'status': 'error', 'error': 'Unauthorized domain.'}, status=403)
    
    try:
        data = json.loads(request.body)
        agent_username = data.get('agent_username', '').strip()
        
        if agent_username:
            # Find the agent user
            agent = get_object_or_404(User, username=agent_username, company=company, role=User.Role.AGENT)
            agent_display_name = agent.agent_name or agent.username
            agent_photo_url = None
            if agent.agent_photo:
                try:
                    agent_photo_url = request.build_absolute_uri(agent.agent_photo.url)
                except Exception:
                    agent_photo_url = agent.agent_photo.url
            return JsonResponse({'status': 'success', 'agent_name': agent_display_name, 'agent_photo_url': agent_photo_url})
        
        return JsonResponse({'status': 'success', 'agent_name': None})
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)

# Visitor chats AI API (public endpoint — secured by domain policy, not CSRF)
@csrf_exempt
@xframe_options_exempt
def chatbot_iframe_chat_api(request, company_id):
    company = get_object_or_404(Company, id=company_id)
    # Check if subscription has expired
    import datetime
    if company.subscription_valid_until and company.subscription_valid_until < datetime.date.today():
        return JsonResponse({'status': 'error', 'error': 'Subscription has expired.'}, status=403)
    # Check if message limit is reached
    if company.message_limit > 0 and company.message_count >= company.message_limit:
        return JsonResponse({'status': 'error', 'error': 'Message limit has been reached.'}, status=403)
    
    # Check if AI agent is active
    if not getattr(company, 'ai_agent_active', True):
        try:
            # Handle user_message and visitor_id resolution
            if request.content_type and 'application/json' in request.content_type:
                data = json.loads(request.body)
                user_message = data.get('message', '').strip()
                visitor_id = data.get('visitor_id') or data.get('visitorId')
                attachment = None
                message_type = 'text'
            else:
                user_message = (request.POST.get('message') or '').strip()
                visitor_id = request.POST.get('visitor_id') or request.POST.get('visitorId')
                attachment = request.FILES.get('attachment')
                message_type = request.POST.get('message_type', 'text')

            if not visitor_id:
                visitor_id = generate_unique_visitor_code(company, length=6)

            # Persist visitor message
            msg = ChatMessage.objects.create(
                company=company,
                sender='visitor',
                content=user_message or f'[{message_type.upper()}]',
                visitor_id=visitor_id or None,
                message_type=message_type,
                attachment=attachment,
            )

            # Get or create escalation
            esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
            if not esc:
                esc = Escalation.objects.create(company=company, message=user_message or '', visitor_id=visitor_id or None)
                ChatMessage.objects.create(
                    company=company,
                    sender='system',
                    content='Escalation created (AI Disabled)',
                    visitor_id=visitor_id or None,
                    escalation=esc,
                )

            # Save bot offline message
            offline_response = "AI Agent is currently offline. A human agent will assist you shortly."
            bot_msg = ChatMessage.objects.create(
                company=company,
                sender='bot',
                content=offline_response,
                visitor_id=visitor_id or None,
                escalation=esc,
            )

            return JsonResponse({
                'status': 'escalate',
                'response': offline_response,
                'message': 'AI Agent is offline.',
                'escalation_id': esc.id,
                'visitor_id': visitor_id,
                'message_id': bot_msg.id
            })
        except Exception as e:
            return JsonResponse({'status': 'error', 'error': str(e)}, status=500)
    # Allow AI responses regardless of request origin.
    # NOTE: we intentionally do NOT enforce `_is_chat_api_allowed` here anymore so
    # that embedded widgets on authorized domains (or third-party sites) receive
    # AI replies. Messages are still persisted under the specified `company` and
    # topic/guardrail logic remains active. Escalation-related and polling endpoints
    # continue to enforce domain checks elsewhere.
    try:
        # Handle both JSON and FormData requests
        if request.content_type and 'application/json' in request.content_type:
            data = json.loads(request.body)
            user_message = data.get('message', '').strip()
            visitor_id = data.get('visitor_id') or data.get('visitorId')
            attachment = None
            message_type = 'text'
        else:
            # FormData request with possible file upload
            user_message = (request.POST.get('message') or '').strip()
            visitor_id = request.POST.get('visitor_id') or request.POST.get('visitorId')
            attachment = request.FILES.get('attachment')
            if attachment and attachment.size > 2 * 1024 * 1024:
                return JsonResponse({'status': 'error', 'error': 'File size exceeds the 2MB limit.'}, status=400)
            message_type = request.POST.get('message_type', 'text')
        
        generated_visitor_id = None
        if not visitor_id:
            try:
                generated_visitor_id = generate_unique_visitor_code(company, length=6)
                visitor_id = generated_visitor_id
            except Exception:
                visitor_id = None
        if not user_message and not attachment:
            return JsonResponse({'status': 'error', 'error': 'Empty message.'}, status=400)
        # Persist visitor message (deferred if text, saved immediately if file upload is present)
        msg = None
        if attachment:
            try:
                msg = ChatMessage.objects.create(
                    company=company,
                    sender='visitor',
                    content=user_message or f'[{message_type.upper()}]',
                    visitor_id=visitor_id or None,
                    message_type=message_type,
                    attachment=attachment,
                )
            except Exception:
                pass

        esc = None

        # If this visitor session already has an escalation claimed by an agent,
        # forward messages to the agent instead of answering via AI (unless AI mode is active).
        try:
            if visitor_id:
                claimed = Escalation.objects.filter(company=company, visitor_id=visitor_id, claimed_by__isnull=False).order_by('-claimed_at').first()
                if claimed:
                    if not claimed.ai_mode_active:
                        # Agent is handling this visitor — save visitor message now if not already saved
                        if not msg:
                            try:
                                msg = ChatMessage.objects.create(
                                    company=company,
                                    sender='visitor',
                                    content=user_message or f'[{message_type.upper()}]',
                                    visitor_id=visitor_id or None,
                                    message_type=message_type,
                                    attachment=attachment if attachment else None,
                                )
                            except Exception:
                                pass
                        return JsonResponse({
                            'status': 'escalate',
                            'message': 'Agent is handling your request',
                            'escalation_id': claimed.id,
                            'visitor_id': visitor_id
                        })
                    else:
                        esc = claimed
        except Exception:
            pass

        # Check if voice reply keyword matches
        matched_voice, voice_reply = check_voice_replies(company, user_message)
        if matched_voice:
            if not msg:
                try:
                    msg = ChatMessage.objects.create(
                        company=company,
                        sender='visitor',
                        content=user_message or f'[{message_type.upper()}]',
                        visitor_id=visitor_id or None,
                        message_type=message_type,
                        attachment=attachment if attachment else None,
                    )
                except Exception:
                    pass
            # If company requests escalation for every message, create/get escalation
            if getattr(company, 'auto_escalate_every_message', False) and not esc:
                esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
                if not esc:
                    esc = Escalation.objects.create(company=company, message=user_message, visitor_id=visitor_id or None)
                    try:
                        ChatMessage.objects.create(
                            company=company,
                            sender='system',
                            content='Escalation created (auto)',
                            visitor_id=visitor_id or None,
                            escalation=esc,
                        )
                    except Exception:
                        pass
            
            # Create bot voice message
            bot_msg = ChatMessage.objects.create(
                company=company,
                sender='bot',
                content=f"[Voice Reply] {voice_reply.keyword}",
                visitor_id=visitor_id or None,
                message_type='voice',
                attachment=voice_reply.audio_file,
                escalation=esc,
            )
            
            att_url = voice_reply.audio_file.url
            if att_url and 'chat-lab.labxit.com' in request.get_host() and att_url.startswith('/media/'):
                att_url = '/chatpro' + att_url
            
            if esc:
                return JsonResponse({
                    'status': 'escalate',
                    'message': 'Forwarded to agent',
                    'escalation_id': esc.id,
                    'visitor_id': visitor_id,
                    'response': '',
                    'message_type': 'voice',
                    'attachment_url': att_url,
                    'message_id': bot_msg.id
                })
            return JsonResponse({
                'status': 'success',
                'response': '',
                'source': 'voice_reply',
                'visitor_id': visitor_id,
                'message_type': 'voice',
                'attachment_url': att_url,
                'message_id': bot_msg.id
            })

        # If company requests escalation for every message, create an escalation now
        try:
            if getattr(company, 'auto_escalate_every_message', False):
                esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
                if not esc:
                    esc = Escalation.objects.create(company=company, message=user_message, visitor_id=visitor_id or None)
                    try:
                        ChatMessage.objects.create(
                            company=company,
                            sender='system',
                            content='Escalation created (auto)',
                            visitor_id=visitor_id or None,
                            escalation=esc,
                        )
                    except Exception:
                        pass
        except Exception:
            pass

        # Removed early return so AI can process non-text messages (like images)

        # Custom form sessions are now managed conversationally by the AI engine
            


        # Detect escalation keywords (e.g., user requests a human)
        # Prioritize escalation so agent requests are not answered by AI or canned responses.
        ESCALATION_KEYWORDS = [r"\bhuman\b", r"\bagent\b", r"হিউম্যান", r"মানব"]
        import re
        for kw in ESCALATION_KEYWORDS:
            try:
                if re.search(kw, user_message, flags=re.IGNORECASE):
                    # record escalation for agents to pick up
                    if not esc:
                        esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
                    if not esc:
                        esc = Escalation.objects.create(company=company, message=user_message, visitor_id=visitor_id or None)
                        # Save escalation link to message
                        try:
                            ChatMessage.objects.create(
                                company=company,
                                sender='system',
                                content='Escalation created',
                                visitor_id=visitor_id or None,
                                escalation=esc,
                            )
                        except Exception:
                            pass
                    break
            except re.error:
                # ignore malformed regex entries
                continue


            
        # Prepare history for AI context
        past_msgs = ChatMessage.objects.filter(
            company=company, 
            visitor_id=visitor_id
        )
        if msg:
            past_msgs = past_msgs.exclude(id=msg.id)
        past_msgs = past_msgs.order_by('-created_at')[:10]
        
        history = []
        for pm in reversed(past_msgs):
            if pm.sender in ['visitor', 'bot', 'agent']:
                role = 'user' if pm.sender == 'visitor' else 'model'
                att_url = pm.attachment.url if pm.attachment else pm.attachment_url
                history.append({
                    'role': role,
                    'content': pm.content or '',
                    'message_type': pm.message_type,
                    'attachment_url': att_url
                })
                
        ai_attachment_url = msg.attachment.url if (msg and msg.attachment) else None

        system_prompt = get_effective_prompt(company, locals().get('user_message') or locals().get('message_text'), visitor_id=visitor_id)
        
        try:
            if company.ai_provider == 'gemini':
                bot_response = get_gemini_response(
                    api_key=company.api_key,
                    model_name=company.model_name,
                    system_prompt=system_prompt,
                    prompt_text=user_message,
                    message_type=message_type,
                    attachment_url=ai_attachment_url,
                    history=history,
                    google_sheet_url=company.google_sheet_url
                )
            elif company.ai_provider == 'openai':
                bot_response = get_openai_response(
                    api_key=company.api_key,
                    model_name=company.model_name,
                    system_prompt=system_prompt,
                    prompt_text=user_message,
                    message_type=message_type,
                    attachment_url=ai_attachment_url,
                    history=history,
                    google_sheet_url=company.google_sheet_url
                )
            else:
                return JsonResponse({'status': 'error', 'error': 'Unsupported provider.'}, status=400)
        except Exception as e:
            bot_response = "Sorry, I encountered an error processing your request."
            try:
                from chatapp.models import IntegrationErrorLog
                IntegrationErrorLog.objects.create(
                    company=company,
                    platform='ai',
                    error_type='AI_GEN_ERROR',
                    error_message=str(e)
                )
            except Exception:
                pass
            
        # ---------------- AI INTENT ROUTING: FORM SUBMISSION ----------------
        if bot_response and 'TRIGGER_FORM_SUBMISSION:' in bot_response:
            try:
                thank_you_msg = bot_response.split('TRIGGER_FORM_SUBMISSION:')[0].strip()
                json_str = bot_response.split('TRIGGER_FORM_SUBMISSION:', 1)[1].strip()
                if '}' in json_str:
                    json_str = json_str[:json_str.rindex('}')+1]
                submission_payload = json.loads(json_str)
                form_id = submission_payload.get('form_id')
                answers = submission_payload.get('answers', {})
                
                from .models import Form, FormSubmission
                form = Form.objects.get(id=form_id, company=company)
                
                FormSubmission.objects.create(
                    form=form,
                    visitor_id=visitor_id,
                    answers=answers,
                    status='pending'
                )
                
                if thank_you_msg:
                    bot_response = thank_you_msg
                else:
                    is_ben = bool(re.search(r'[\u0980-\u09FF]', user_message)) if user_message else False
                    if is_ben:
                        bot_response = f"ধন্যবাদ! '{form.title}' এর জন্য আপনার তথ্য সফলভাবে জমা দেওয়া হয়েছে।"
                    else:
                        bot_response = f"Thank you! Your information for '{form.title}' has been submitted successfully."
            except Exception as e:
                bot_response = "I'm sorry, I couldn't process the form submission."
        # --------------------------------------------------------------------

        # ---------------- AI INTENT ROUTING: ECOM ORDER ----------------
        if bot_response and 'TRIGGER_ECOM_ORDER:' in bot_response:
            try:
                json_str = bot_response.split('TRIGGER_ECOM_ORDER:', 1)[1].strip()
                if '}' in json_str:
                    json_str = json_str[:json_str.rindex('}')+1]
                order_payload = json.loads(json_str)
                
                if hasattr(company, 'ecom_api_url') and company.ecom_api_url:
                    api_url = company.ecom_api_url
                    if not api_url.endswith('/'):
                        api_url += '/'
                    order_api_url = api_url + 'api/bot/order/'
                    headers = {'Authorization': f'Bearer {company.ecom_api_key}', 'Content-Type': 'application/json'}
                    
                    order_res = requests.post(order_api_url, json=order_payload, headers=headers, timeout=10)
                    if order_res.status_code == 200:
                        order_data = order_res.json()
                        tracking_id = order_data.get('tracking_id', '')
                        is_ben = bool(re.search(r'[\u0980-\u09FF]', user_message)) if user_message else False
                        if is_ben:
                            bot_response = f"অভিনন্দন! আপনার অর্ডারটি সফলভাবে প্লেস করা হয়েছে।\nঅর্ডার ট্র্যাকিং আইডি: {tracking_id}"
                        else:
                            bot_response = f"Congratulations! Your order has been successfully placed.\nOrder Tracking ID: {tracking_id}"
                    else:
                        bot_response = f"I'm sorry, failed to place the order: {order_res.json().get('message', 'Unknown error')}"
                else:
                    bot_response = "I'm sorry, e-commerce integration is not configured."
            except Exception:
                bot_response = "I'm sorry, I couldn't process the order request properly."
        # ----------------------------------------------------------

        # ---------------- AI INTENT ROUTING: MEDIA ----------------
        import re
        media_matches = re.findall(r'\[MEDIA:(\d+)\]', bot_response.strip() if bot_response else '', re.IGNORECASE)
        if media_matches:
            if not msg:
                try:
                    ChatMessage.objects.create(
                        company=company,
                        sender='visitor',
                        content=user_message or f'[{message_type.upper()}]',
                        visitor_id=visitor_id or None,
                        message_type=message_type,
                        attachment=attachment if attachment else None,
                    )
                except Exception:
                    pass
            try:
                media_id = int(media_matches[0])
                from .models import CompanyMedia
                media_item = CompanyMedia.objects.get(id=media_id, company=company)
                
                cleaned_resp = re.sub(r'\[MEDIA:\d+\]', '', bot_response, flags=re.IGNORECASE).strip()
                bot_response = cleaned_resp if cleaned_resp else "Here is the picture you requested."
                
                # persist bot response as an image message
                try:
                    bot_msg = ChatMessage.objects.create(
                        company=company,
                        sender='bot',
                        content=bot_response,
                        visitor_id=visitor_id or None,
                        escalation=esc,
                        message_type='image',
                        attachment=media_item.image
                    )
                    bot_msg_id = bot_msg.id
                except Exception:
                    bot_msg_id = None
                    
                ai_source = 'gemini' if company.ai_provider == 'gemini' else 'openai'
                att_url = media_item.image.url if media_item.image else None
                if att_url and 'chat-lab.labxit.com' in request.get_host() and att_url.startswith('/media/'):
                    att_url = '/chatpro' + att_url
                    
                if len(media_matches) > 1:
                    for extra_id in media_matches[1:]:
                        try:
                            extra_item = CompanyMedia.objects.get(id=int(extra_id), company=company)
                            ChatMessage.objects.create(
                                company=company,
                                sender='bot',
                                content='',
                                visitor_id=visitor_id or None,
                                escalation=esc,
                                message_type='image',
                                attachment=extra_item.image
                            )
                        except Exception:
                            pass
                            
                for extra_file in media_item.extra_files.all():
                    try:
                        ChatMessage.objects.create(
                            company=company,
                            sender='bot',
                            content='',
                            visitor_id=visitor_id or None,
                            escalation=esc,
                            message_type='image',
                            attachment=extra_file.image
                        )
                    except Exception:
                        pass
                if esc:
                    return JsonResponse({
                        'status': 'success', 
                        'response': bot_response, 
                        'source': ai_source, 
                        'escalation_id': esc.id,
                        'visitor_id': visitor_id,
                        'message_type': 'image',
                        'attachment_url': att_url,
                        'message_id': bot_msg_id
                    })
                return JsonResponse({
                    'status': 'success', 
                    'response': bot_response, 
                    'source': ai_source, 
                    'visitor_id': visitor_id,
                    'message_type': 'image',
                    'attachment_url': att_url,
                    'message_id': bot_msg_id
                })
            except Exception as e:
                import traceback
                traceback.print_exc()
                bot_response = f"I'm sorry, I couldn't find the requested picture. Debug Error: {str(e)}"
        # ----------------------------------------------------------

        # persist bot response
        if not msg:
            try:
                ChatMessage.objects.create(
                    company=company,
                    sender='visitor',
                    content=user_message or f'[{message_type.upper()}]',
                    visitor_id=visitor_id or None,
                    message_type=message_type,
                    attachment=attachment if attachment else None,
                )
            except Exception:
                pass
        try:
            bot_msg = ChatMessage.objects.create(
                company=company,
                sender='bot',
                content=bot_response,
                visitor_id=visitor_id or None,
                escalation=esc,
            )
            bot_msg_id = bot_msg.id
        except Exception:
            bot_msg_id = None
        if esc:
            return JsonResponse({
                'status': 'escalate',
                'message': 'Forwarded to agent',
                'escalation_id': esc.id,
                'visitor_id': visitor_id,
                'response': bot_response,
                'message_id': bot_msg_id
            })
        return JsonResponse({'status': 'success', 'response': bot_response, 'source': 'ai', 'visitor_id': visitor_id, 'message_id': bot_msg_id})
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)


@csrf_exempt
def chatbot_poll_messages(request, company_id):
    """Poll for messages for a given visitor session. Query params: visitor_id, since_id"""
    company = get_object_or_404(Company, id=company_id)
    if not _is_domain_allowed(request, company):
        return JsonResponse({'status': 'error', 'error': 'Unauthorized domain.'}, status=403)
    visitor_id = request.GET.get('visitor_id') or request.GET.get('visitorId')
    since_id = int(request.GET.get('since_id') or 0)
    if not visitor_id:
        return JsonResponse({'status': 'error', 'error': 'visitor_id required.'}, status=400)
    msgs = ChatMessage.objects.filter(company=company, visitor_id=visitor_id, id__gt=since_id).order_by('id')
    out = []
    for m in msgs:
        msg_data = {
            'id': m.id,
            'sender': m.sender,
            'content': m.content,
            'message_type': m.message_type,
            'created_at': m.created_at.isoformat(),
        }
        if m.attachment:
            att_url = m.attachment.url
            if 'chat-lab.labxit.com' in request.get_host() and att_url.startswith('/media/'):
                att_url = '/chatpro' + att_url
            msg_data['attachment'] = att_url
        out.append(msg_data)
    return JsonResponse({'status': 'success', 'messages': out})


@csrf_exempt
def chatbot_escalation_status(request, company_id):
    """Check if an escalation is claimed. Query params: escalation_id"""
    company = get_object_or_404(Company, id=company_id)
    if not _is_domain_allowed(request, company):
        return JsonResponse({'status': 'error', 'error': 'Unauthorized domain.'}, status=403)
    esc_id = request.GET.get('escalation_id') or request.GET.get('escalationId')
    if not esc_id:
        return JsonResponse({'status': 'error', 'error': 'escalation_id required.'}, status=400)
    esc = get_object_or_404(Escalation, id=esc_id, company=company)
    if esc.claimed_by:
        agent_display = esc.claimed_by.agent_name or esc.claimed_by.username
        agent_photo_url = None
        if esc.claimed_by.agent_photo:
            try:
                agent_photo_url = request.build_absolute_uri(esc.claimed_by.agent_photo.url)
            except Exception:
                agent_photo_url = esc.claimed_by.agent_photo.url
        return JsonResponse({'status': 'claimed', 'agent_name': agent_display, 'agent_photo_url': agent_photo_url})
    return JsonResponse({'status': 'pending'})


@admin_required
def admin_escalations_list(request):
    company = get_admin_company(request)
    escs = company.escalations.filter(claimed_by__isnull=True).order_by('created_at')
    return render(request, 'admin/escalation_list.html', {'escalations': escs})


@admin_required
@require_POST
def admin_claim_escalation(request, pk):
    company = get_admin_company(request)
    esc = get_object_or_404(Escalation, pk=pk, company=company)
    esc.claimed_by = request.user
    esc.claimed_at = timezone.now()
    esc.is_handled = True
    esc.save()
    # add a system chat message indicating agent joined
    try:
        ChatMessage.objects.create(company=company, sender='system', content=f"Agent {request.user.agent_name or request.user.username} joined.", visitor_id=esc.visitor_id, escalation=esc)
    except Exception:
        pass
    messages.success(request, 'Escalation claimed and agent notified.')
    return redirect('admin_escalations_list')


@admin_required
@require_POST
def admin_agent_send_message(request, company_id):
    company = get_admin_company(request)
    data = request.POST or {}
    visitor_id = data.get('visitor_id') or request.POST.get('visitor_id')
    message = data.get('message') or request.POST.get('message')
    if not visitor_id or not message:
        messages.error(request, 'visitor_id and message are required.')
        return redirect('admin_escalations_list')
    try:
        msg = ChatMessage.objects.create(company=company, sender='agent', content=message, visitor_id=visitor_id)
        # Send to Meta API immediately if visitor is on a social channel
        if visitor_id and (visitor_id.startswith('fb_') or visitor_id.startswith('wa_')):
            from .utils.social_sender import send_outgoing_social_message
            threading.Thread(
                target=send_outgoing_social_message,
                args=(msg.id,)
            ).start()
        messages.success(request, 'Message sent to visitor.')
    except Exception:
        messages.error(request, 'Failed to send message.')
    return redirect('admin_escalations_list')


# ============ REAL-TIME MESSAGING ============

def get_visitor_profile(visitor_id, company):
    if not visitor_id or not visitor_id.startswith('fb_') or not company.fb_page_access_token:
        return {'name': None, 'pic': None}
    
    from django.core.cache import cache
    cache_key = f"visitor_profile_{visitor_id}"
    cached = cache.get(cache_key)
    if cached is not None:
        return cached

    psid = visitor_id.replace('fb_', '')
    profile = {'name': None, 'pic': None}
    try:
        import requests as req_lib
        url = f"https://graph.facebook.com/v17.0/{psid}?fields=first_name,last_name,profile_pic&access_token={company.fb_page_access_token}"
        resp = req_lib.get(url, timeout=3)
        if resp.status_code == 200:
            data = resp.json()
            fn = data.get('first_name', '')
            ln = data.get('last_name', '')
            if fn or ln:
                profile['name'] = f"{fn} {ln}".strip()
            profile['pic'] = data.get('profile_pic')
            cache.set(cache_key, profile, 60*60*24)
        else:
            cache.set(cache_key, profile, 60*60)
    except Exception:
        cache.set(cache_key, profile, 300)
    
    return profile

@agent_required
def agent_get_pending_escalations(request):
    """Poll for new pending escalations without page refresh (API endpoint)."""
    company = request.user.company
    # Get pending escalations (unclaimed) ordered by creation time
    pending = company.escalations.filter(claimed_by__isnull=True).order_by('-updated_at').values(
        'id', 'message', 'visitor_id', 'created_at', 'updated_at'
    )[:10]  # limit to 10 most recent
    
    active = company.escalations.filter(claimed_by=request.user).order_by('-updated_at').values(
        'id', 'message', 'visitor_id', 'created_at', 'updated_at', 'claimed_at', 'ai_mode_active'
    )[:20]

    out_pending = []
    for esc in pending:
        prof = get_visitor_profile(esc['visitor_id'], company)
        out_pending.append({
            'id': esc['id'],
            'message': esc['message'][:100],  # truncate long messages
            'visitor_id': esc['visitor_id'],
            'visitor_name': prof['name'],
            'visitor_profile_pic': prof['pic'],
            'created_at': esc['created_at'].isoformat(),
            'updated_at': esc['updated_at'].isoformat(),
        })
        
    out_active = []
    for esc in active:
        prof = get_visitor_profile(esc['visitor_id'], company)
        out_active.append({
            'id': esc['id'],
            'message': esc['message'][:100],
            'visitor_id': esc['visitor_id'],
            'visitor_name': prof['name'],
            'visitor_profile_pic': prof['pic'],
            'created_at': esc['created_at'].isoformat(),
            'updated_at': esc['updated_at'].isoformat(),
            'claimed_at': esc['claimed_at'].isoformat() if esc['claimed_at'] else None,
            'ai_mode_active': esc['ai_mode_active']
        })
        
    return JsonResponse({'status': 'success', 'escalations': out_pending, 'active': out_active})


@agent_or_admin_required
@require_POST
def agent_update_typing_status(request, visitor_id):
    """Agent indicates they are typing. Updates escalation's agent_last_typing_at."""
    company = request.user.company
    claim = company.escalations.filter(visitor_id=visitor_id, claimed_by=request.user).order_by('-created_at').first()
    if not claim:
        return JsonResponse({'status': 'error', 'error': 'Not your conversation.'}, status=403)
    # Update the typing timestamp
    claim.agent_last_typing_at = timezone.now()
    claim.save(update_fields=['agent_last_typing_at'])
    return JsonResponse({'status': 'success'})


@agent_or_admin_required
@require_POST
def agent_toggle_ai_mode(request, visitor_id):
    """Toggle AI auto-response mode for a claimed conversation."""
    company = request.user.company
    claim = company.escalations.filter(visitor_id=visitor_id, claimed_by=request.user).order_by('-created_at').first()
    if not claim:
        messages.error(request, "No claimed escalation found for this visitor.")
        return redirect('agent_dashboard')
        
    # Check if AI agent is active for the company when attempting to activate AI mode
    if not getattr(company, 'ai_agent_active', True) and not claim.ai_mode_active:
        messages.error(request, "AI Agent mode is turned off by Admin.")
        return redirect('agent_chat', visitor_id=visitor_id)

    claim.ai_mode_active = not claim.ai_mode_active
    claim.save(update_fields=['ai_mode_active'])
    status_str = "activated" if claim.ai_mode_active else "deactivated"
    messages.success(request, f"AI Mode {status_str} for this conversation.")
    return redirect('agent_chat', visitor_id=visitor_id)


@csrf_exempt
def chatbot_get_agent_typing_status(request, company_id):
    """Check if agent is currently typing (within last 3 seconds)."""
    company = get_object_or_404(Company, id=company_id)
    if not _is_domain_allowed(request, company):
        return JsonResponse({'status': 'error', 'error': 'Unauthorized domain.'}, status=403)
    
    escalation_id = request.GET.get('escalation_id') or request.GET.get('escalationId')
    if not escalation_id:
        return JsonResponse({'status': 'error', 'error': 'escalation_id required.'}, status=400)
    
    esc = get_object_or_404(Escalation, id=escalation_id, company=company)
    
    # Check if agent typed in the last 3 seconds
    if esc.agent_last_typing_at:
        elapsed = (timezone.now() - esc.agent_last_typing_at).total_seconds()
        is_typing = elapsed < 3
    else:
        is_typing = False
    
    return JsonResponse({'status': 'success', 'agent_is_typing': is_typing})


# Superuser Package Management
@superuser_required
def superuser_package_list(request):
    from .models import Package
    packages = Package.objects.all().order_by('-created_at')
    return render(request, 'superuser/package_list.html', {'packages': packages})


@superuser_required
def superuser_package_create(request):
    from .models import Package
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        is_unlimited = request.POST.get('is_unlimited') == 'on'
        message_limit = int(request.POST.get('message_limit') or 0)
        days_valid = int(request.POST.get('days_valid') or 30)
        price = float(request.POST.get('price') or 0.0)
        
        if name:
            try:
                Package.objects.create(
                    name=name,
                    is_unlimited=is_unlimited,
                    message_limit=message_limit,
                    days_valid=days_valid,
                    price=price
                )
                messages.success(request, f'Package "{name}" created successfully.')
                return redirect('superuser_package_list')
            except Exception as e:
                messages.error(request, f'Error: {str(e)}')
        else:
            messages.error(request, 'Package name is required.')
            
    return render(request, 'superuser/package_form.html', {'object': None})


@superuser_required
def superuser_package_edit(request, pk):
    from .models import Package
    package = get_object_or_404(Package, pk=pk)
    if request.method == 'POST':
        name = request.POST.get('name', '').strip()
        is_unlimited = request.POST.get('is_unlimited') == 'on'
        message_limit = int(request.POST.get('message_limit') or 0)
        days_valid = int(request.POST.get('days_valid') or 30)
        price = float(request.POST.get('price') or 0.0)
        
        if name:
            try:
                package.name = name
                package.is_unlimited = is_unlimited
                package.message_limit = message_limit
                package.days_valid = days_valid
                package.price = price
                package.save()
                messages.success(request, f'Package "{name}" updated successfully.')
                return redirect('superuser_package_list')
            except Exception as e:
                messages.error(request, f'Error: {str(e)}')
        else:
            messages.error(request, 'Package name is required.')
            
    return render(request, 'superuser/package_form.html', {'object': package})


@superuser_required
def superuser_package_delete(request, pk):
    from .models import Package
    package = get_object_or_404(Package, pk=pk)
    if request.method == 'POST':
        name = package.name
        package.delete()
        messages.success(request, f'Package "{name}" deleted successfully.')
    return redirect('superuser_package_list')


# Superuser AI Model Management
@superuser_required
def superuser_model_list(request):
    from .models import AIModel
    models = AIModel.objects.all().order_by('provider', '-created_at')
    return render(request, 'superuser/model_list.html', {'ai_models': models})


@superuser_required
def superuser_model_create(request):
    from .models import AIModel
    if request.method == 'POST':
        provider = request.POST.get('provider', '').strip()
        model_name = request.POST.get('model_name', '').strip()
        display_name = request.POST.get('display_name', '').strip()
        if provider and model_name and display_name:
            try:
                AIModel.objects.create(provider=provider, model_name=model_name, display_name=display_name)
                messages.success(request, f'AI Model "{display_name}" added successfully.')
            except Exception as e:
                messages.error(request, f'Error: {str(e)}')
        return redirect('superuser_model_list')
    provider_choices = AIModel.PROVIDER_CHOICES
    return render(request, 'superuser/model_form.html', {'provider_choices': provider_choices})


@superuser_required
def superuser_model_edit(request, pk):
    from .models import AIModel
    model = get_object_or_404(AIModel, pk=pk)
    if request.method == 'POST':
        model.provider = request.POST.get('provider', '').strip()
        model.model_name = request.POST.get('model_name', '').strip()
        model.display_name = request.POST.get('display_name', '').strip()
        try:
            model.save()
            messages.success(request, 'AI Model updated successfully.')
        except Exception as e:
            messages.error(request, f'Error: {str(e)}')
        return redirect('superuser_model_list')
    provider_choices = AIModel.PROVIDER_CHOICES
    return render(request, 'superuser/model_form.html', {'object': model, 'provider_choices': provider_choices})


@superuser_required
def superuser_model_delete(request, pk):
    from .models import AIModel
    model = get_object_or_404(AIModel, pk=pk)
    if request.method == 'POST':
        display_name = model.display_name
        model.delete()
        messages.success(request, f'AI Model "{display_name}" deleted.')
    return redirect('superuser_model_list')


# Admin Theme Management
@admin_required
def admin_theme_settings(request):
    """Edit theme for company."""
    company = get_admin_company(request)
    from .models import Theme
    theme, created = Theme.objects.get_or_create(company=company)
    
    if request.method == 'POST':
        preset = request.POST.get('preset', 'custom')
        theme.preset = preset
        
        # Apply preset colors if selected
        if preset == 'dark':
            theme.primary_color = '#6366f1'
            theme.secondary_color = '#10b981'
            theme.background_color = '#0b0f19'
            theme.text_primary = '#f5f5f5'
            theme.text_muted = '#9ca3af'
            theme.border_color = '#374151'
            theme.bot_bubble_bg = '#1f2937'
            theme.user_bubble_bg = '#6366f1'
            theme.send_button_bg = '#6366f1'
        elif preset == 'light':
            theme.primary_color = '#3b82f6'
            theme.secondary_color = '#10b981'
            theme.background_color = '#ffffff'
            theme.text_primary = '#1f2937'
            theme.text_muted = '#6b7280'
            theme.border_color = '#e5e7eb'
            theme.bot_bubble_bg = '#f3f4f6'
            theme.user_bubble_bg = '#3b82f6'
            theme.send_button_bg = '#3b82f6'
        
        # If custom, update individual colors
        if preset == 'custom':
            theme.primary_color = request.POST.get('primary_color', theme.primary_color)
            theme.secondary_color = request.POST.get('secondary_color', theme.secondary_color)
            theme.background_color = request.POST.get('background_color', theme.background_color)
            theme.text_primary = request.POST.get('text_primary', theme.text_primary)
            theme.text_muted = request.POST.get('text_muted', theme.text_muted)
            theme.border_color = request.POST.get('border_color', theme.border_color)
            theme.bot_bubble_bg = request.POST.get('bot_bubble_bg', theme.bot_bubble_bg)
            theme.user_bubble_bg = request.POST.get('user_bubble_bg', theme.user_bubble_bg)
            theme.send_button_bg = request.POST.get('send_button_bg', theme.send_button_bg)
        
        theme.save()
        messages.success(request, 'Theme updated successfully.')
        return redirect('admin_theme_settings')
    
    return render(request, 'admin/theme_settings.html', {'theme': theme, 'preset': theme.preset})


# ============ SOCIAL MEDIA WEBHOOKS ============

def process_incoming_social_message(company_id, visitor_id, user_message, meta_message_id=None, webhook_log_id=None, message_type='text', attachment_url=None, reply_to_mid=None, request_host=None):
    from django.db import IntegrityError
    from .models import WebhookTestLog
    import time
    
    start_time = time.time()
    
    def update_webhook_log(status):
        if webhook_log_id:
            try:
                elapsed = round(time.time() - start_time, 2)
                WebhookTestLog.objects.filter(id=webhook_log_id).update(reply_status=status, reply_time_seconds=elapsed)
            except Exception:
                pass

    try:
        try:
            company = Company.objects.get(id=company_id)
        except Company.DoesNotExist:
            update_webhook_log('failed')
            return

        # Check if subscription has expired
        import datetime
        if company.subscription_valid_until and company.subscription_valid_until < datetime.date.today():
            update_webhook_log('ignored')
            return

        # Check if AI Agent is active
        if not getattr(company, 'ai_agent_active', True):
            if meta_message_id and not ChatMessage.objects.filter(meta_message_id=meta_message_id).exists():
                try:
                    ChatMessage.objects.create(
                        company=company,
                        sender='visitor',
                        content=user_message,
                        visitor_id=visitor_id,
                        meta_message_id=meta_message_id,
                        message_type=message_type,
                        attachment_url=attachment_url,
                    )
                except Exception:
                    pass
            # Create/update escalation
            esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
            if not esc:
                esc = Escalation.objects.create(company=company, message=user_message, visitor_id=visitor_id)
                try:
                    ChatMessage.objects.create(
                        company=company,
                        sender='system',
                        content='Escalation created (AI Disabled)',
                        visitor_id=visitor_id,
                        escalation=esc,
                    )
                except Exception:
                    pass
            update_webhook_log('escalated')
            return

        # Check if message limit is reached
        if company.message_limit > 0 and company.message_count >= company.message_limit:
            update_webhook_log('ignored')
            return

        # Deduplicate incoming social messages
        if meta_message_id:
            if ChatMessage.objects.filter(meta_message_id=meta_message_id).exists():
                update_webhook_log('ignored')
                return

        # 1. Save visitor message is deferred until after AI reply is sent to speed up Meta delivery

        # ── Immediately signal to Messenger: seen + typing indicator ──────────────
        # This creates the LazyChat-style experience:
        #   1. User's message shows as 'Seen' (eye icon)
        #   2. The '...' typing bubble appears immediately
        #   3. The AI reply lands after generation — feels instant & human-like
        if visitor_id.startswith('fb_'):
            from .utils.social_sender import send_fb_mark_seen, send_fb_typing_on
            _fb_psid = visitor_id[3:]
            if getattr(company, 'fb_mark_seen_enabled', True):
                send_fb_mark_seen(company, _fb_psid)   # Show 'Seen' checkmark
            if getattr(company, 'fb_typing_indicator_enabled', True):
                send_fb_typing_on(company, _fb_psid)   # Show '...' typing dots
        # ─────────────────────────────────────────────────────────────────────────

        # 2. If claimed by agent, do nothing (agent dashboard handles it) unless AI mode is active
        claimed = Escalation.objects.filter(company=company, visitor_id=visitor_id, claimed_by__isnull=False).order_by('-claimed_at').first()
        if claimed and not claimed.ai_mode_active:
            claimed.message = user_message
            claimed.save(update_fields=['message'])
            try:
                ChatMessage.objects.create(
                    company=company,
                    sender='visitor',
                    content=user_message,
                    visitor_id=visitor_id,
                    meta_message_id=meta_message_id,
                    message_type=message_type,
                    attachment_url=attachment_url,
                )
            except IntegrityError:
                pass
            update_webhook_log('escalated')
            return

        esc = claimed if (claimed and claimed.ai_mode_active) else None

        # Check if voice reply keyword matches
        matched_voice, voice_reply = check_voice_replies(company, user_message)
        if matched_voice:
            if not esc:
                esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
            if not esc:
                esc = Escalation.objects.create(company=company, message=user_message, visitor_id=visitor_id)
                try:
                    ChatMessage.objects.create(
                        company=company,
                        sender='system',
                        content='Escalation created (auto)',
                        visitor_id=visitor_id,
                        escalation=esc,
                    )
                except Exception:
                    pass

            def build_abs_url(audio_file):
                if not audio_file: return None
                d = request_host if request_host else 'chat-lab.labxit.com'
                d = d.replace('http://', '').replace('https://', '').rstrip('/')
                a_url = audio_file.url
                if 'chat-lab.labxit.com' in d and a_url.startswith('/media/') and not d.endswith('/chatpro'):
                    a_url = '/chatpro' + a_url
                return f"https://{d}{a_url}"
                
            voice_attachment_url = build_abs_url(voice_reply.audio_file)
            
            delivery_status = 'sent'
            if visitor_id.startswith('fb_') or visitor_id.startswith('wa_'):
                from .utils.social_sender import send_social_reply_direct
                success = send_social_reply_direct(company, visitor_id, '', attachment_url=voice_attachment_url, message_type='voice')
                delivery_status = 'sent' if success else 'failed'
                
            try:
                ChatMessage.objects.create(
                    company=company,
                    sender='visitor',
                    content=user_message,
                    visitor_id=visitor_id,
                    meta_message_id=meta_message_id,
                    message_type=message_type,
                    attachment_url=attachment_url,
                )
            except IntegrityError:
                pass
                
            ChatMessage.objects.create(
                company=company,
                sender='bot',
                content=f"[Voice Reply] {voice_reply.keyword}",
                visitor_id=visitor_id,
                escalation=esc,
                delivery_status=delivery_status,
                delivery_attempts=1,
                message_type='voice',
                attachment=voice_reply.audio_file,
                attachment_url=voice_attachment_url
            )
            update_webhook_log('sent')
            return
        # 3. Auto-escalate every message check OR social messages
        if getattr(company, 'auto_escalate_every_message', False) or visitor_id.startswith('fb_') or visitor_id.startswith('wa_'):
            if not esc:
                esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
            if not esc:
                esc = Escalation.objects.create(company=company, message=user_message, visitor_id=visitor_id)
                try:
                    ChatMessage.objects.create(
                        company=company,
                        sender='system',
                        content='Escalation created (auto)',
                        visitor_id=visitor_id,
                        escalation=esc,
                    )
                except Exception:
                    pass
            else:
                esc.message = user_message
                esc.save(update_fields=['message'])

        # 4. Check escalation keywords
        ESCALATION_KEYWORDS = [r"\bhuman\b", r"\bagent\b", r"হিউম্যান", r"মানব"]
        import re
        for kw in ESCALATION_KEYWORDS:
            if re.search(kw, user_message, flags=re.IGNORECASE):
                if not esc:
                    esc = Escalation.objects.filter(company=company, visitor_id=visitor_id, is_handled=False).order_by('-created_at').first()
                if not esc:
                    esc = Escalation.objects.create(company=company, message=user_message, visitor_id=visitor_id)
                    ChatMessage.objects.create(
                        company=company,
                        sender='system',
                        content='Escalation created',
                        visitor_id=visitor_id,
                        escalation=esc,
                    )
                break

        # Custom form sessions are now managed conversationally by the AI engine
            



        # Fetch conversation history for AI context
        # Retrieve the last 10 messages
        past_msgs = ChatMessage.objects.filter(
            company=company, 
            visitor_id=visitor_id
        ).order_by('-created_at')[:10]
        
        # Reverse to chronological order
        history = []
        for pm in reversed(past_msgs):
            if pm.sender in ['visitor', 'bot', 'agent']:
                role = 'user' if pm.sender == 'visitor' else 'model'
                att_url = pm.attachment.url if pm.attachment else pm.attachment_url
                history.append({
                    'role': role,
                    'content': pm.content or '',
                    'message_type': pm.message_type,
                    'attachment_url': att_url
                })

        # 6. Generate AI response
        system_prompt = get_effective_prompt(company, locals().get('user_message') or locals().get('message_text'), visitor_id=visitor_id)
        
        ai_prompt_text = user_message
        ai_attachment_url = attachment_url
        ai_message_type = message_type
        
        if reply_to_mid:
            replied_msg = ChatMessage.objects.filter(meta_message_id=reply_to_mid).first()
            if replied_msg:
                rep_att_url = replied_msg.attachment.url if replied_msg.attachment else replied_msg.attachment_url
                if rep_att_url:
                    ai_attachment_url = rep_att_url
                    ai_message_type = replied_msg.message_type
                    ai_prompt_text = f"[User is replying to an image] {user_message}"
                else:
                    ai_prompt_text = f"[Replying to previous message: '{replied_msg.content}'] {user_message}"
                    
        try:
            if company.ai_provider == 'gemini':
                bot_response = get_gemini_response(
                    api_key=company.api_key,
                    model_name=company.model_name,
                    system_prompt=system_prompt,
                    prompt_text=ai_prompt_text,
                    message_type=ai_message_type,
                    attachment_url=ai_attachment_url,
                    history=history,
                    google_sheet_url=company.google_sheet_url
                )
            elif company.ai_provider == 'openai':
                bot_response = get_openai_response(
                    api_key=company.api_key,
                    model_name=company.model_name,
                    system_prompt=system_prompt,
                    prompt_text=ai_prompt_text,
                    message_type=ai_message_type,
                    attachment_url=ai_attachment_url,
                    history=history,
                    google_sheet_url=company.google_sheet_url
                )
            else:
                bot_response = "AI configuration error: unsupported provider."
        except Exception as e:
            bot_response = "Sorry, I encountered an error processing your request."
            try:
                from chatapp.models import IntegrationErrorLog
                IntegrationErrorLog.objects.create(
                    company=company,
                    platform='ai',
                    error_type='AI_GEN_ERROR',
                    error_message=str(e)
                )
            except Exception:
                pass
            # ── Still send the error message back to the user on Messenger ───
            try:
                delivery_status = 'sent'
                if visitor_id.startswith('fb_') or visitor_id.startswith('wa_'):
                    from .utils.social_sender import send_social_reply_direct
                    success = send_social_reply_direct(company, visitor_id, bot_response)
                    delivery_status = 'sent' if success else 'failed'

                try:
                    ChatMessage.objects.create(
                        company=company,
                        sender='visitor',
                        content=user_message,
                        visitor_id=visitor_id,
                        meta_message_id=meta_message_id,
                        message_type=message_type,
                        attachment_url=attachment_url,
                    )
                except IntegrityError:
                    pass

                err_msg = ChatMessage.objects.create(
                    company=company,
                    sender='bot',
                    content=bot_response,
                    visitor_id=visitor_id,
                    escalation=esc,
                    delivery_status=delivery_status,
                    delivery_attempts=1
                )
            except Exception:
                pass
            update_webhook_log('failed')
            return

        # ---------------- AI INTENT ROUTING: FORM SUBMISSION ----------------
        if bot_response and 'TRIGGER_FORM_SUBMISSION:' in bot_response:
            try:
                thank_you_msg = bot_response.split('TRIGGER_FORM_SUBMISSION:')[0].strip()
                json_str = bot_response.split('TRIGGER_FORM_SUBMISSION:', 1)[1].strip()
                if '}' in json_str:
                    json_str = json_str[:json_str.rindex('}')+1]
                submission_payload = json.loads(json_str)
                form_id = submission_payload.get('form_id')
                answers = submission_payload.get('answers', {})
                
                from .models import Form, FormSubmission
                form = Form.objects.get(id=form_id, company=company)
                
                FormSubmission.objects.create(
                    form=form,
                    visitor_id=visitor_id,
                    answers=answers,
                    status='pending'
                )
                
                if thank_you_msg:
                    bot_response = thank_you_msg
                else:
                    is_ben = bool(re.search(r'[\u0980-\u09FF]', user_message)) if user_message else False
                    if is_ben:
                        bot_response = f"ধন্যবাদ! '{form.title}' এর জন্য আপনার তথ্য সফলভাবে জমা দেওয়া হয়েছে।"
                    else:
                        bot_response = f"Thank you! Your information for '{form.title}' has been submitted successfully."
            except Exception as e:
                bot_response = "I'm sorry, I couldn't process the form submission."
        # --------------------------------------------------------------------

        # ---------------- AI INTENT ROUTING: ECOM ORDER ----------------
        if bot_response and 'TRIGGER_ECOM_ORDER:' in bot_response:
            try:
                json_str = bot_response.split('TRIGGER_ECOM_ORDER:', 1)[1].strip()
                if '}' in json_str:
                    json_str = json_str[:json_str.rindex('}')+1]
                order_payload = json.loads(json_str)
                
                if hasattr(company, 'ecom_api_url') and company.ecom_api_url:
                    api_url = company.ecom_api_url
                    if not api_url.endswith('/'):
                        api_url += '/'
                    order_api_url = api_url + 'api/bot/order/'
                    headers = {'Authorization': f'Bearer {company.ecom_api_key}', 'Content-Type': 'application/json'}
                    
                    order_res = requests.post(order_api_url, json=order_payload, headers=headers, timeout=10)
                    if order_res.status_code == 200:
                        order_data = order_res.json()
                        tracking_id = order_data.get('tracking_id', '')
                        is_ben = bool(re.search(r'[\u0980-\u09FF]', user_message)) if user_message else False
                        if is_ben:
                            bot_response = f"অভিনন্দন! আপনার অর্ডারটি সফলভাবে প্লেস করা হয়েছে।\nঅর্ডার ট্র্যাকিং আইডি: {tracking_id}"
                        else:
                            bot_response = f"Congratulations! Your order has been successfully placed.\nOrder Tracking ID: {tracking_id}"
                    else:
                        bot_response = f"I'm sorry, failed to place the order: {order_res.json().get('message', 'Unknown error')}"
                else:
                    bot_response = "I'm sorry, e-commerce integration is not configured."
            except Exception:
                bot_response = "I'm sorry, I couldn't process the order request properly."
        # ----------------------------------------------------------

        # ---------------- AI INTENT ROUTING: MEDIA ----------------
        media_attachment_url = None
        media_msg_type = 'text'
        import re
        media_matches = re.findall(r'\[MEDIA:(\d+)\]', bot_response.strip() if bot_response else '', re.IGNORECASE)
        extra_attachments = []
        if media_matches:
            try:
                media_id = int(media_matches[0])
                from .models import CompanyMedia
                media_item = CompanyMedia.objects.get(id=media_id, company=company)
                
                cleaned_resp = re.sub(r'\[MEDIA:\d+\]', '', bot_response, flags=re.IGNORECASE).strip()
                bot_response = cleaned_resp if cleaned_resp else "Here is the picture you requested."
                media_msg_type = 'image'
                
                def build_abs_url(img):
                    if not img: return None
                    d = request_host if request_host else 'chat-lab.labxit.com'
                    d = d.replace('http://', '').replace('https://', '').rstrip('/')
                    a_url = img.url
                    if 'chat-lab.labxit.com' in d and a_url.startswith('/media/') and not d.endswith('/chatpro'):
                        a_url = '/chatpro' + a_url
                    return f"https://{d}{a_url}"
                    
                media_attachment_url = build_abs_url(media_item.image)
                
                if len(media_matches) > 1:
                    for extra_id in media_matches[1:]:
                        try:
                            ex_item = CompanyMedia.objects.get(id=int(extra_id), company=company)
                            ex_url = build_abs_url(ex_item.image)
                            if ex_url: extra_attachments.append(ex_url)
                        except Exception:
                            pass
                            
                for extra_file in media_item.extra_files.all():
                    ex_url = build_abs_url(extra_file.image)
                    if ex_url: extra_attachments.append(ex_url)
            except Exception as e:
                import traceback
                traceback.print_exc()
                bot_response = f"I'm sorry, I couldn't find the requested picture. Debug Error: {str(e)}"
        # ----------------------------------------------------------

        delivery_status = 'sent'
        if visitor_id.startswith('fb_') or visitor_id.startswith('wa_'):
            from .utils.social_sender import send_social_reply_direct
            success = send_social_reply_direct(company, visitor_id, bot_response, attachment_url=media_attachment_url, message_type=media_msg_type)
            delivery_status = 'sent' if success else 'failed'
            
            for ex_url in extra_attachments:
                send_social_reply_direct(company, visitor_id, '', attachment_url=ex_url, message_type='image')

        try:
            ChatMessage.objects.create(
                company=company,
                sender='visitor',
                content=user_message,
                visitor_id=visitor_id,
                meta_message_id=meta_message_id,
                message_type=message_type,
                attachment_url=attachment_url,
            )
        except IntegrityError:
            # Already processed
            update_webhook_log('ignored')
            return

        msg = ChatMessage.objects.create(
            company=company,
            sender='bot',
            content=bot_response,
            visitor_id=visitor_id,
            escalation=esc,
            delivery_status=delivery_status,
            delivery_attempts=1,
            message_type=media_msg_type,
            attachment_url=media_attachment_url
        )
        
        for ex_url in extra_attachments:
            ChatMessage.objects.create(
                company=company,
                sender='bot',
                content='',
                visitor_id=visitor_id,
                escalation=esc,
                delivery_status='sent',
                delivery_attempts=1,
                message_type='image',
                attachment_url=ex_url
            )
            
        update_webhook_log('replied')
    except Exception as ex:
        import logging
        logging.getLogger(__name__).exception("Unhandled error processing social message: %s", ex)
        update_webhook_log('failed')


def _process_fb_feed_change(feed_company, change, wh_log=None):
    import logging as _log
    import traceback
    _logger_feed = _log.getLogger(__name__)

    value = change.get('value', {})
    item_type = value.get('item')
    verb = value.get('verb')

    # Log all feed events to monitor — even non-comment ones
    _logger_feed.info(
        "FB feed event: company=%s, item=%s, verb=%s, value_keys=%s",
        feed_company.name, item_type, verb, list(value.keys())
    )

    # For sample/test payloads, if item is status or page post, we can process it as comment
    # to facilitate end-to-end testing
    is_sample = False
    if wh_log and "[EVENT_TYPE: sample/" in wh_log.raw_payload:
        is_sample = True

    if is_sample and item_type == 'status':
        item_type = 'comment'

    # Only process new top-level comments (not replies to comments)
    if item_type == 'comment' and verb == 'add':
        sender_id = value.get('from', {}).get('id')
        # Don't process comments from the page itself
        if not sender_id or (sender_id == feed_company.fb_page_id and not is_sample):
            return

        comment_id = value.get('comment_id') or f"test_comment_{value.get('post_id', 'unknown')}"
        post_id = value.get('post_id')
        sender_name = value.get('from', {}).get('name', '')
        message_text = value.get('message', '')
        parent_id = value.get('parent_id', '')  # exists if it's a reply to another comment

        _logger_feed.info(
            "FB comment received: company=%s, sender=%s (%s), comment_id=%s, "
            "post_id=%s, parent_id=%s, message_preview=%s",
            feed_company.name, sender_name, sender_id,
            comment_id, post_id, parent_id, (message_text or '')[:60]
        )

        if not comment_id or not message_text:
            _logger_feed.warning(
                "FB comment missing comment_id or message, skipping. "
                "comment_id=%s, message=%s", comment_id, bool(message_text)
            )
            return

        from .models import FacebookComment
        if FacebookComment.objects.filter(comment_id=comment_id).exists():
            _logger_feed.info("FB comment %s already in DB, skipping.", comment_id)
            return  # Already processed

        # Fetch profile picture
        profile_pic = None
        # Don't fetch for sample ids to save time and API quota
        if not is_sample and feed_company.fb_page_access_token:
            try:
                import requests as req_lib
                pic_url = (
                    f"https://graph.facebook.com/{sender_id}"
                    f"?fields=profile_pic,picture"
                    f"&access_token={feed_company.fb_page_access_token}"
                )
                resp = req_lib.get(pic_url, timeout=5)
                if resp.status_code == 200:
                    pic_data = resp.json()
                    profile_pic = pic_data.get('profile_pic')
                    if not profile_pic:
                        profile_pic = pic_data.get('picture', {}).get('data', {}).get('url')
            except Exception:
                pass

        # Save comment to database
        fb_comment = FacebookComment.objects.create(
            company=feed_company,
            comment_id=comment_id,
            post_id=post_id,
            sender_id=sender_id,
            sender_name=sender_name,
            message=message_text,
            profile_pic=profile_pic,
        )
        _logger_feed.info(
            "FB comment saved to DB: id=%s, company=%s, sender=%s",
            fb_comment.id, feed_company.name, sender_name
        )

        # Update wh_log to show comment info
        if wh_log:
            try:
                extra_note = (
                    f"\n\n[COMMENT EVENT PARSED]\n"
                    f"Company: {feed_company.name}\n"
                    f"Sender: {sender_name} (ID: {sender_id})\n"
                    f"Comment ID: {comment_id}\n"
                    f"Post ID: {post_id}\n"
                    f"Message: {message_text[:200]}\n"
                    f"AI Reply Enabled: {feed_company.fb_comment_ai_reply}"
                )
                wh_log.raw_payload = wh_log.raw_payload + extra_note
                wh_log.save(update_fields=['raw_payload'])
            except Exception:
                pass

        # ── AI Auto-Reply ──────────────────────────
        if feed_company.fb_comment_ai_reply:
            try:
                # Build system prompt
                system_prompt = get_effective_prompt(feed_company, message_text)

                # Generate AI response
                ai_reply_text = None
                if feed_company.ai_provider == 'gemini':
                    ai_reply_text = get_gemini_response(
                        api_key=feed_company.api_key,
                        model_name=feed_company.model_name,
                        system_prompt=system_prompt,
                        prompt_text=message_text,
                        message_type='text',
                        attachment_url=None,
                        history=[]
                    )
                elif feed_company.ai_provider == 'openai':
                    ai_reply_text = get_openai_response(
                        api_key=feed_company.api_key,
                        model_name=feed_company.model_name,
                        system_prompt=system_prompt,
                        prompt_text=message_text,
                        message_type='text',
                        attachment_url=None,
                        history=[]
                    )

                if ai_reply_text:
                    # Post reply to Facebook comment
                    import requests as req_lib
                    from django.utils import timezone as _tz
                    reply_api_url = (
                        f"https://graph.facebook.com/v17.0/{comment_id}/comments"
                    )
                    reply_payload = {
                        "message": ai_reply_text,
                        "access_token": feed_company.fb_page_access_token or "dummy_token_for_testing",
                    }
                    reply_resp = req_lib.post(reply_api_url, json=reply_payload, timeout=10)

                    if reply_resp.status_code == 200:
                        fb_comment.ai_reply = ai_reply_text
                        fb_comment.is_handled = True
                        fb_comment.replied_at = _tz.now()
                        fb_comment.save(update_fields=['ai_reply', 'is_handled', 'replied_at'])
                        _logger_feed.info(
                            "AI replied to FB comment %s for company %s",
                            comment_id, feed_company.name
                        )
                        # Update wh_log reply status
                        if wh_log:
                            try:
                                wh_log.reply_status = 'replied'
                                wh_log.save(update_fields=['reply_status'])
                            except Exception:
                                pass
                    else:
                        err_detail = reply_resp.text[:300]
                        _logger_feed.warning(
                            "Failed to post AI reply to FB comment %s: %s",
                            comment_id, err_detail
                        )
                        # Update wh_log with failure
                        if wh_log:
                            try:
                                wh_log.raw_payload += f"\n\n[AI REPLY FAILED]\n{err_detail}"
                                wh_log.reply_status = 'failed'
                                wh_log.save(update_fields=['raw_payload', 'reply_status'])
                            except Exception:
                                pass
                        
                        # Log to IntegrationErrorLog
                        from .models import IntegrationErrorLog
                        IntegrationErrorLog.objects.create(
                            company=feed_company,
                            platform='facebook',
                            error_type='COMMENT_REPLY_FAILED',
                            error_message=f"Failed to post AI reply to comment ID {comment_id}",
                            details=f"Facebook API Response: {err_detail}"
                        )
            except Exception as _ai_ex:
                _logger_feed.exception(
                    "Error generating/sending AI reply for FB comment %s: %s",
                    comment_id, _ai_ex
                )
                # Comment stays pending — agent can reply manually
                if wh_log:
                    try:
                        wh_log.reply_status = 'failed'
                        wh_log.save(update_fields=['reply_status'])
                    except Exception:
                        pass
                
                # Log to IntegrationErrorLog
                from .models import IntegrationErrorLog
                IntegrationErrorLog.objects.create(
                    company=feed_company,
                    platform='facebook',
                    error_type='COMMENT_AI_EXCEPTION',
                    error_message=str(_ai_ex),
                    details=traceback.format_exc()
                )


@csrf_exempt
def facebook_webhook(request):
    from django.http import HttpResponse, HttpResponseForbidden
    from .models import SiteSettings, WebhookTestLog
    import json as _json

    settings_obj = SiteSettings.objects.first()
    app_verify_token = settings_obj.fb_app_verify_token if settings_obj else None
    raw_ip = (request.META.get('HTTP_X_FORWARDED_FOR') or request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()
    wh_log = None
    import logging as _wh_logger
    _logger = _wh_logger.getLogger(__name__)

    # ── Always log incoming POST payload ──────────────────────────────────────
    # Logging is unconditional so we can always see what Facebook is sending.
    try:
        if request.method == 'GET':
            payload_str = f"GET Verification Request:\nQuery Parameters: {_json.dumps(dict(request.GET.items()), ensure_ascii=False, indent=2)}"
            event_header = "[EVENT_TYPE: webhook_verification]"
        else:
            raw_body = request.body.decode('utf-8', errors='replace')
            # Detect event type from payload before full processing
            event_header = "[EVENT_TYPE: unknown]"
            try:
                _peek = _json.loads(raw_body)
                _entries = _peek.get('entry', [])
                _types = []
                for _e in _entries:
                    if _e.get('messaging'):
                        _types.append('messaging')
                    if _e.get('changes'):
                        for _ch in _e['changes']:
                            _types.append(f"feed/{_ch.get('field','?')}")
                    if not _e.get('messaging') and not _e.get('changes'):
                        _types.append('other')
                if 'sample' in _peek:
                    _field = _peek['sample'].get('field', 'unknown')
                    _types.append(f"sample/{_field}")
                event_header = f"[EVENT_TYPE: {', '.join(_types) if _types else 'unknown'}]"
            except Exception:
                pass
            payload_str = event_header + "\n\n" + raw_body

        wh_log = WebhookTestLog.objects.create(
            source='facebook',
            raw_payload=payload_str,
            remote_ip=raw_ip or None,
        )
        # Keep only last 100 logs
        old_ids = list(WebhookTestLog.objects.order_by('-id').values_list('id', flat=True)[100:])
        if old_ids:
            WebhookTestLog.objects.filter(id__in=old_ids).delete()
    except Exception as _log_ex:
        _logger.error("Failed to log facebook webhook request: %s", _log_ex)


    if request.method == 'GET':
        mode = request.GET.get('hub.mode')
        token = request.GET.get('hub.verify_token')
        challenge = request.GET.get('hub.challenge')
        
        if mode == 'subscribe' and token and token == app_verify_token:
            return HttpResponse(challenge, content_type="text/plain")
        else:
            return HttpResponseForbidden("Verification token mismatch")
            
    elif request.method == 'POST':
        try:
            body = json.loads(request.body)

            # ── Handle Facebook Developer Portal test payload ──────────────────
            # When you click "Send to Server" in the Webhooks field test panel,
            # Facebook sends a wrapper payload like:
            # {"sample": {"field": "messages", "value": {"sender":{...}, ...}}}
            if 'sample' in body and 'object' not in body:
                import logging
                logger = logging.getLogger(__name__)
                logger.info("Facebook webhook test payload received (Developer Portal): %s", body)

                # Process the test payload as a message or feed if it contains valid parameters
                try:
                    sample_data = body.get('sample', {})
                    value = sample_data.get('value', {})
                    
                    if sample_data.get('field') == 'feed':
                        # Find company matching the sender/page id
                        from_id = value.get('from', {}).get('id')
                        feed_company = Company.objects.filter(fb_page_id=from_id).first()
                        if not feed_company:
                            feed_company = Company.objects.first()
                            if feed_company:
                                logger.info("No company matched sample Page ID %s. Routing to fallback company: %s", from_id, feed_company.name)
                        
                        if feed_company:
                            _process_fb_feed_change(feed_company, {"field": "feed", "value": value}, wh_log)
                        else:
                            if wh_log:
                                wh_log.reply_status = 'failed'
                                wh_log.raw_payload += "\n\n[ERROR] No companies found in the database. Add a company first to test."
                                wh_log.save(update_fields=['reply_status', 'raw_payload'])
                    else:
                        if value:
                            sender_id = value.get('sender', {}).get('id')
                            recipient_id = value.get('recipient', {}).get('id')
                            message_text = value.get('message', {}).get('text')
                            
                            if sender_id and recipient_id and message_text:
                                company = Company.objects.filter(fb_page_id=recipient_id, fb_messenger_enabled=True).first()
                                if not company:
                                    company = Company.objects.first()
                                if company:
                                    meta_message_id = value.get('message', {}).get('mid')
                                    process_incoming_social_message(
                                        company.id, f"fb_{sender_id}", message_text,
                                        meta_message_id=meta_message_id,
                                        webhook_log_id=wh_log.id if wh_log else None,
                                        request_host=request.get_host()
                                    )
                                    logger.info("Successfully routed sample message for company %s", company.name)
                except Exception as _proc_ex:
                    logger.error("Error processing sample message: %s", _proc_ex)

                return HttpResponse("TEST_RECEIVED", content_type="text/plain")

            # ── Standard live event payload ────────────────────────────────────
            if body.get('object') == 'page':
                for entry in body.get('entry', []):
                    page_id = entry.get('id')

                    # ── Messaging events (Messenger DMs) ─────────────────────
                    # Only process if Messenger integration is enabled
                    messenger_company = Company.objects.filter(fb_page_id=page_id, fb_messenger_enabled=True).first()
                    if messenger_company:
                        for messaging_event in entry.get('messaging', []):
                            if messaging_event.get('message'):
                                msg_data = messaging_event['message']
                                if msg_data.get('is_echo'):
                                    continue
                                message_text = msg_data.get('text', '')
                                attachments = msg_data.get('attachments', [])
                                
                                if message_text or attachments:
                                    sender_id = messaging_event['sender']['id']
                                    meta_message_id = msg_data.get('mid')
                                    
                                    message_type = 'text'
                                    attachment_url = None
                                    
                                    if attachments:
                                        att = attachments[0]
                                        att_type = att.get('type')
                                        if att_type in ['image', 'video', 'audio', 'file']:
                                            message_type = 'voice' if att_type == 'audio' else att_type
                                            attachment_url = att.get('payload', {}).get('url')

                                    reply_to_mid = msg_data.get('reply_to', {}).get('mid')

                                    process_incoming_social_message(
                                        messenger_company.id, f"fb_{sender_id}", message_text,
                                        meta_message_id=meta_message_id,
                                        webhook_log_id=wh_log.id if wh_log else None,
                                        message_type=message_type,
                                        attachment_url=attachment_url,
                                        reply_to_mid=reply_to_mid,
                                        request_host=request.get_host()
                                    )

                    # ── Feed/Comment changes ──────────────────────────────────
                    # Use any company matching the page_id (messenger not required)
                    feed_company = Company.objects.filter(fb_page_id=page_id).first()
                    if not feed_company:
                        # Log for debugging — page_id has no matching company
                        import logging as _log
                        _log.getLogger(__name__).warning(
                            "FB webhook: received feed/changes for page_id=%s but no company found. "
                            "Check that fb_page_id is configured correctly.",
                            page_id
                        )
                        # Update wh_log with this info
                        if wh_log:
                            from .models import WebhookTestLog
                            wh_log.reply_status = 'ignored'
                            wh_log.save(update_fields=['reply_status'])

                    if feed_company:
                        for change in entry.get('changes', []):
                            _process_fb_feed_change(feed_company, change, wh_log)

            return HttpResponse("EVENT_RECEIVED")
        except Exception as e:
            import logging as _log
            _log.getLogger(__name__).exception("Unhandled error in facebook_webhook: %s", e)
            return HttpResponse(str(e), status=400)






@csrf_exempt
def whatsapp_webhook(request):
    from django.http import HttpResponse, HttpResponseForbidden
    from .models import SiteSettings, WebhookTestLog
    import json as _json

    settings_obj = SiteSettings.objects.first()
    app_verify_token = settings_obj.fb_app_verify_token if settings_obj else None
    raw_ip = (request.META.get('HTTP_X_FORWARDED_FOR') or request.META.get('REMOTE_ADDR', '')).split(',')[0].strip()

    wh_log = None
    # Log incoming request (GET/POST) for debugging in WebhookTestLog
    try:
        if settings_obj and settings_obj.webhook_logging_enabled:
            if request.method == 'GET':
                payload_str = f"GET Verification Request:\nQuery Parameters: {_json.dumps(dict(request.GET.items()), ensure_ascii=False, indent=2)}"
            else:
                payload_str = request.body.decode('utf-8', errors='replace')
                
            wh_log = WebhookTestLog.objects.create(
                source='whatsapp',
                raw_payload=payload_str,
                remote_ip=raw_ip or None,
            )
            # Keep only last 50 logs
            old_ids = list(WebhookTestLog.objects.values_list('id', flat=True)[50:])
            if old_ids:
                WebhookTestLog.objects.filter(id__in=old_ids).delete()
    except Exception as _log_ex:
        import logging
        logging.getLogger(__name__).error("Failed to log whatsapp webhook request: %s", _log_ex)

    if request.method == 'GET':
        mode = request.GET.get('hub.mode')
        token = request.GET.get('hub.verify_token')
        challenge = request.GET.get('hub.challenge')
        
        if mode == 'subscribe' and token and token == app_verify_token:
            return HttpResponse(challenge, content_type="text/plain")
        else:
            return HttpResponseForbidden("Verification token mismatch")
            
    elif request.method == 'POST':
        try:
            body = json.loads(request.body)
            if body.get('object') == 'whatsapp_business_account':
                for entry in body.get('entry', []):
                    for change in entry.get('changes', []):
                        value = change.get('value', {})
                        metadata = value.get('metadata', {})
                        phone_id = metadata.get('phone_number_id')
                        
                        company = Company.objects.filter(wa_phone_number_id=phone_id, whatsapp_enabled=True).first()
                        if not company:
                            continue
                            
                        if 'messages' in value:
                            for msg in value['messages']:
                                msg_type = msg.get('type')
                                if msg_type in ['text', 'image', 'audio', 'video', 'document']:
                                    sender_phone = msg['from']
                                    meta_message_id = msg.get('id')
                                    
                                    message_text = ""
                                    message_type = 'text'
                                    attachment_url = None
                                    
                                    if msg_type == 'text':
                                        message_text = msg.get('text', {}).get('body', '')
                                    else:
                                        message_type = 'voice' if msg_type == 'audio' else msg_type
                                        message_type = 'file' if message_type == 'document' else message_type
                                        media_data = msg.get(msg_type, {})
                                        attachment_url = media_data.get('id') or media_data.get('link')
                                        message_text = media_data.get('caption', '')
                                    
                                    reply_to_mid = msg.get('context', {}).get('id')
                                    
                                    if message_text or attachment_url:
                                        process_incoming_social_message(
                                            company.id, f"wa_{sender_phone}", message_text,
                                            meta_message_id=meta_message_id,
                                            webhook_log_id=wh_log.id if wh_log else None,
                                            message_type=message_type,
                                            attachment_url=attachment_url,
                                            reply_to_mid=reply_to_mid,
                                            request_host=request.get_host()
                                        )
                                    
            return HttpResponse("EVENT_RECEIVED")
        except Exception as e:
            return HttpResponse(str(e), status=400)


def privacy_policy_view(request):
    from .models import SiteSettings
    settings_obj = SiteSettings.objects.first()
    privacy_html = settings_obj.privacy_policy if settings_obj else None
    
    if not privacy_html:
        # Default compliant privacy policy
        privacy_html = """<h2>ChatLab Privacy Policy</h2>
<p class="effective-date">Effective Date: June 19, 2026</p>

<p>This Privacy Policy explains how ChatLab ("we", "us", or "our") collects, uses, processes, and protects your information when you use our omnichannel chatbot platform, customer support agent desk, and integrations with Meta APIs (including Facebook Messenger and WhatsApp Business APIs).</p>

<h3>1. Information We Collect and Process</h3>
<p>To provide our automated communication and support desk services, we process the following categories of data:</p>
<ul>
    <li><strong>Account Information:</strong> For registered business administrators and customer support agents, we collect names, email addresses, business names, passwords, and profile photos to manage account access.</li>
    <li><strong>Meta Integration Data:</strong> To enable automated replies and agent handovers on social channels, we process Page-Scoped IDs (PSID) from Facebook Messenger and WhatsApp phone numbers from the WhatsApp Business API. This technical data is required to map incoming messages to chat threads and route automated or manual responses back to the visitor.</li>
    <li><strong>Communication Content & Logs:</strong> We store the content of incoming and outgoing chat messages, message metadata (timestamps, delivery status), and user-uploaded media (such as voice notes, images, and document attachments) to display conversations in the agent desk and power AI bot responses.</li>
    <li><strong>System Interaction Data:</strong> Technical logs, including client IP addresses and user agent headers, are collected for system security, rate limiting, and domain-authorization checks of the chat widget.</li>
</ul>

<h3>2. How the System Operates and Uses Data</h3>
<p>ChatLab processes data for specific functional purposes to help businesses serve their users:</p>
<ul>
    <li><strong>AI Chatbot Generation:</strong> When a user sends a message to a connected business chatbot, the message text is forwarded to large language model (LLM) engines (specifically Google Gemini or OpenAI APIs) to generate an automated contextual reply. No personally identifying account data or access tokens are shared with these AI providers.</li>
    <li><strong>Live Handoff and Escalations:</strong> If the AI chatbot cannot answer a question or if a visitor requests a human, the system logs an escalation. This allows human agents to claim the conversation, review the recent message history, and respond directly via our agent desk.</li>
    <li><strong>E-commerce and Sitemap Syncing:</strong> If configured, our system accesses client-configured sitemaps or product databases to scrape public product details (e.g. item availability and pricing) and answer visitor queries accurately.</li>
</ul>

<h3>3. Third-Party Data Sharing and Processors</h3>
<p>We do not rent, sell, or trade personal data or visitor identifiers. We share information only with trusted processors necessary to deliver our services, including:</p>
<ul>
    <li><strong>AI API Providers:</strong> Google Gemini and OpenAI are used solely for the real-time generation of chatbot responses.</li>
    <li><strong>Hosting and Database Infrastructure:</strong> Data is hosted securely on encrypted cloud servers with industry-standard access controls.</li>
</ul>

<h3>4. Compliance with Meta Developer Policies</h3>
<p>Our Meta integrations strictly comply with Meta's Developer Policies. Page-Scoped IDs (PSIDs) and WhatsApp contact identifiers are processed solely for the functional purpose of message delivery and thread organization. We do not use Facebook or WhatsApp data for marketing profiling, retargeting, or advertising.</p>

<h3>5. Data Retention and Deletion Request Instructions</h3>
<p>We retain conversation data and logs as long as the respective business's account is active, or as required by our clients. If you wish to request the deletion of your personal data or conversation records, you may do so at any time:</p>
<p><strong>How to Request Deletion:</strong> Send a written deletion request to our support team at <a href="mailto:info@chat-lab.labxit.com">info@chat-lab.labxit.com</a>. We will process and confirm your request, including the removal of all associated social media identifiers (PSIDs, phone numbers, and chat logs) from our database within 30 days of receipt.</p>

<h3>6. Security Measures</h3>
<p>We implement rigorous technical and organizational security measures, including transport layer security (HTTPS) and encryption of credentials at rest, to prevent unauthorized access, alteration, or disclosure of communications and tokens.</p>

<h3>7. Policy Updates</h3>
<p>We may update this Privacy Policy to reflect changes in our service or regulatory updates. Any changes will be published on this page with an updated effective date.</p>

<h3>8. Contact Information</h3>
<p>For questions, feedback, or inquiries regarding this policy, please reach out to us at <a href="mailto:info@chat-lab.labxit.com">info@chat-lab.labxit.com</a>.</p>"""

    return render(request, 'public/privacy_policy.html', {'privacy_policy': privacy_html})


def privacy_policy_logo_view(request):
    from .models import SiteSettings
    from django.http import HttpResponse, Http404
    import os
    
    settings_obj = SiteSettings.objects.first()
    
    # Try to serve custom logo if uploaded
    if settings_obj and settings_obj.logo:
        try:
            logo_path = settings_obj.logo.path
            if os.path.exists(logo_path):
                with open(logo_path, 'rb') as f:
                    content = f.read()
                ext = os.path.splitext(logo_path)[1].lower()
                content_type = 'image/png' if ext == '.png' else 'image/jpeg'
                return HttpResponse(content, content_type=content_type)
        except Exception:
            pass
            
    # Fallback to static logo.jpg
    from django.conf import settings as django_settings
    static_logo_path = os.path.join(django_settings.BASE_DIR, 'chatapp', 'static', 'chatapp', 'images', 'logo.jpg')
    
    if os.path.exists(static_logo_path):
        with open(static_logo_path, 'rb') as f:
            content = f.read()
        return HttpResponse(content, content_type='image/jpeg')
        
    # Second fallback to logo.png
    static_logo_png_path = os.path.join(django_settings.BASE_DIR, 'chatapp', 'static', 'chatapp', 'images', 'logo.png')
    if os.path.exists(static_logo_png_path):
        with open(static_logo_png_path, 'rb') as f:
            content = f.read()
        return HttpResponse(content, content_type='image/png')
        
    raise Http404("Logo not found")




@superuser_required
def superuser_privacy_policy(request):
    from .models import SiteSettings
    settings_obj, created = SiteSettings.objects.get_or_create(id=1)
    
    if request.method == 'POST':
        privacy_policy = request.POST.get('privacy_policy', '').strip()
        settings_obj.privacy_policy = privacy_policy
        settings_obj.save()
        messages.success(request, "Privacy Policy updated successfully.")
        return redirect('superuser_privacy_policy')
        
    return render(request, 'superuser/privacy_policy_form.html', {'settings': settings_obj})


@superuser_required
def superuser_settings(request):
    from .models import SiteSettings
    settings_obj, created = SiteSettings.objects.get_or_create(id=1)
    
    if request.method == 'POST':
        fb_app_verify_token = request.POST.get('fb_app_verify_token', '').strip()
        if not fb_app_verify_token.isdigit() or len(fb_app_verify_token) != 12:
            messages.error(request, "Verification token must be exactly 12 numeric digits.")
        else:
            settings_obj.fb_app_verify_token = fb_app_verify_token
            settings_obj.save()
            messages.success(request, "Global site configurations saved successfully.")
            return redirect('superuser_settings')
            
    return render(request, 'superuser/settings.html', {'settings': settings_obj})


def public_documentation(request):
    return render(request, 'public/documentation.html', {
        'host_url': request.build_absolute_uri('/')[:-1]
    })

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
@require_POST
def public_test_developer_api(request):
    url = request.POST.get('api_url', '').strip()
    key = request.POST.get('api_key', '').strip()
    
    if not url or not key:
        return JsonResponse({'status': 'error', 'message': 'API Base URL and API Key are required'})
        
    try:
        if not url.endswith('/'):
            url += '/'
        import requests
        test_url = url + 'api/bot/test/'
        catalog_url = url + 'api/bot/catalog/'
        
        headers = {'Authorization': f'Bearer {key}'}
        
        # Test 1: Basic connection check
        res = requests.get(test_url, headers=headers, timeout=10)
        if res.status_code != 200:
            return JsonResponse({'status': 'error', 'message': f'Connection to {test_url} failed: HTTP {res.status_code}. Response: {res.text}'})
        
        try:
            test_data = res.json()
            if test_data.get('status') != 'success':
                return JsonResponse({'status': 'error', 'message': f'API Error from /test/: {test_data.get("message")}'})
        except:
            return JsonResponse({'status': 'error', 'message': f'Invalid API Endpoint at {test_url}. Server did not return valid JSON.'})
            
        # Test 2: Catalog format check
        cat_res = requests.get(catalog_url, headers=headers, timeout=10)
        data = []
        if cat_res.status_code == 200:
            try:
                j = cat_res.json()
                data = j.get('products', [])
            except:
                return JsonResponse({'status': 'error', 'message': f'Catalog endpoint {catalog_url} did not return valid JSON.'})
        else:
            return JsonResponse({'status': 'error', 'message': f'Catalog connection to {catalog_url} failed: HTTP {cat_res.status_code}. Response: {cat_res.text}'})
                
        return JsonResponse({'status': 'success', 'message': 'Connection successful! Your API endpoints are configured correctly.', 'data': data})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': f'Exception occurred: {str(e)}'})

@superuser_required
def superuser_monitor(request):
    from .models import Company, IntegrationErrorLog, SiteSettings
    companies = Company.objects.all().order_by('-created_at')
    
    settings_obj, _ = SiteSettings.objects.get_or_create(id=1)
    
    # Get last 20 error logs
    error_logs = IntegrationErrorLog.objects.all().order_by('-created_at')[:20]
    
    if request.method == 'POST':
        action = request.POST.get('action')
        if action == 'clear_logs':
            IntegrationErrorLog.objects.all().delete()
            messages.success(request, "All diagnostic logs cleared successfully.")
            return redirect('superuser_monitor')
        elif action == 'toggle_logging':
            settings_obj.webhook_logging_enabled = not settings_obj.webhook_logging_enabled
            settings_obj.save()
            status = "enabled" if settings_obj.webhook_logging_enabled else "disabled"
            messages.success(request, f"Webhook logging is now {status}.")
            return redirect('superuser_monitor')
        elif action == 'clear_webhook_test_logs':
            from .models import WebhookTestLog
            WebhookTestLog.objects.all().delete()
            messages.success(request, "Webhook test logs cleared.")
            return redirect('superuser_monitor')

        elif action == 'clear_fb_comments':
            from .models import FacebookComment
            FacebookComment.objects.all().delete()
            messages.success(request, "সব Facebook comment log মুছে ফেলা হয়েছে।")
            return redirect('superuser_monitor')

    from .models import WebhookTestLog, FacebookComment
    webhook_test_logs = WebhookTestLog.objects.order_by('-created_at')[:30]
    fb_comments_log = FacebookComment.objects.select_related('company').order_by('-created_at')[:50]

    return render(request, 'superuser/monitor.html', {
        'companies': companies,
        'error_logs': error_logs,
        'webhook_test_logs': webhook_test_logs,
        'fb_comments_log': fb_comments_log,
        'site_settings': settings_obj,
    })


@superuser_required
def superuser_fb_comments_log(request):
    """JSON polling endpoint — returns recent Facebook comments for the monitor page."""
    from .models import FacebookComment
    since_id = request.GET.get('since_id')
    qs = FacebookComment.objects.select_related('company').order_by('-created_at')
    if since_id:
        try:
            # Return comments newer than since_id (higher id = newer)
            qs = qs.filter(id__gt=int(since_id))
        except (ValueError, TypeError):
            pass
    comments = list(qs[:30].values(
        'id', 'comment_id', 'post_id', 'sender_id', 'sender_name',
        'message', 'ai_reply', 'is_handled', 'replied_at', 'created_at',
        'company__name', 'company__fb_page_id'
    ))
    for c in comments:
        c['created_at'] = c['created_at'].strftime('%Y-%m-%d %H:%M:%S') if c['created_at'] else ''
        c['replied_at'] = c['replied_at'].strftime('%H:%M:%S') if c['replied_at'] else ''
    return JsonResponse({'comments': comments, 'count': len(comments)})



@superuser_required
def superuser_monitor_webhook_logs(request):
    """JSON polling endpoint — returns the latest webhook test log entries."""
    from .models import WebhookTestLog
    since_id = request.GET.get('since_id')
    qs = WebhookTestLog.objects.all()
    if since_id:
        try:
            qs = qs.filter(id__gt=int(since_id))
        except (ValueError, TypeError):
            pass
    logs = list(qs.order_by('-created_at')[:20].values(
        'id', 'source', 'raw_payload', 'remote_ip', 'created_at', 'reply_status', 'reply_time_seconds'
    ))
    for log in logs:
        log['created_at'] = log['created_at'].strftime('%Y-%m-%d %H:%M:%S UTC')
    return JsonResponse({'logs': logs})


@superuser_required
@require_POST
def test_facebook_connection(request, company_id):
    import requests as req_lib
    company = get_object_or_404(Company, id=company_id)
    fb_page_id = request.POST.get('fb_page_id', '').strip()
    fb_page_access_token = request.POST.get('fb_page_access_token', '').strip()

    if not fb_page_id or not fb_page_access_token:
        return JsonResponse({'status': 'error', 'error': 'Page ID and Access Token are required.'}, status=400)

    # ── Strategy: query the page directly by its ID using the token.
    # This only requires the Page Access Token to be valid for that page —
    # no special 'pages_read_engagement' permission needed.
    url = f"https://graph.facebook.com/v17.0/{fb_page_id}?fields=id,name&access_token={fb_page_access_token}"
    try:
        response = req_lib.get(url, timeout=10)
        res_data = response.json()
        if response.status_code == 200:
            verified_id = res_data.get('id')
            verified_name = res_data.get('name', '(unknown)')
            if verified_id == fb_page_id:
                return JsonResponse({
                    'status': 'success',
                    'message': f"Connected successfully to Page '{verified_name}' (ID: {verified_id}). Token is valid ✔"
                })
            else:
                return JsonResponse({
                    'status': 'warning',
                    'message': f"Token works but returned Page ID {verified_id} — expected {fb_page_id}. Check the Page ID field."
                })
        else:
            err = res_data.get('error', {})
            err_msg = err.get('message', 'Unknown Facebook API error')
            err_code = err.get('code', '')
            # Offer more helpful guidance for common errors
            if err_code == 190:
                err_msg = f"Invalid or expired Page Access Token. Please generate a fresh token. (FB code {err_code})"
            elif err_code == 100:
                err_msg = f"Page ID not found or token lacks permission for this page. (FB code {err_code})"
            return JsonResponse({'status': 'error', 'error': err_msg}, status=400)
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)


@superuser_required
@require_POST
def test_whatsapp_connection(request, company_id):
    import requests
    company = get_object_or_404(Company, id=company_id)
    wa_phone_number_id = request.POST.get('wa_phone_number_id', '').strip()
    wa_access_token = request.POST.get('wa_access_token', '').strip()
    
    if not wa_phone_number_id or not wa_access_token:
        return JsonResponse({'status': 'error', 'error': 'Phone Number ID and Access Token are required.'}, status=400)
        
    url = f"https://graph.facebook.com/v17.0/{wa_phone_number_id}?access_token={wa_access_token}"
    try:
        response = requests.get(url, timeout=10)
        res_data = response.json()
        if response.status_code == 200:
            return JsonResponse({
                'status': 'success',
                'message': f"Connected successfully. WhatsApp Business Verified (ID: {wa_phone_number_id})."
            })
        else:
            err_msg = res_data.get('error', {}).get('message', 'Unknown WhatsApp API error')
            return JsonResponse({'status': 'error', 'error': err_msg}, status=400)
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)


@superuser_required
@require_POST
def send_test_facebook_message(request, company_id):
    import requests
    company = get_object_or_404(Company, id=company_id)
    fb_page_access_token = request.POST.get('fb_page_access_token', '').strip()
    message_text = request.POST.get('message', '').strip()
    
    if not fb_page_access_token or not message_text:
        return JsonResponse({'status': 'error', 'error': 'Access Token and message text are required.'}, status=400)
        
    # Find the most recent Facebook Messenger chat session for this company
    recent_msg = company.messages.filter(visitor_id__startswith='fb_').order_by('-created_at').first()
    if not recent_msg or not recent_msg.visitor_id:
        return JsonResponse({
            'status': 'error',
            'error': 'No recent Facebook visitor found in chat logs. Please send a message to your Facebook Page first to register a recipient.'
        }, status=400)
        
    psid = recent_msg.visitor_id.replace('fb_', '')
    url = f"https://graph.facebook.com/v17.0/me/messages?access_token={fb_page_access_token}"
    payload = {
        "recipient": {"id": psid},
        "message": {"text": message_text}
    }
    
    try:
        response = requests.post(url, json=payload, timeout=10)
        res_data = response.json()
        if response.status_code == 200:
            # Save the outgoing test message in our database so it displays in chat history
            ChatMessage.objects.create(
                company=company,
                sender='bot',
                content=message_text,
                visitor_id=recent_msg.visitor_id,
                delivery_status='sent'
            )
            return JsonResponse({
                'status': 'success',
                'message': f"Test message pushed successfully to PSID: {psid}."
            })
        else:
            err_msg = res_data.get('error', {}).get('message', 'Failed to send message via Facebook.')
            return JsonResponse({'status': 'error', 'error': err_msg}, status=400)
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)


@superuser_required
@require_POST
def send_test_whatsapp_message(request, company_id):
    import requests
    company = get_object_or_404(Company, id=company_id)
    wa_phone_number_id = request.POST.get('wa_phone_number_id', '').strip()
    wa_access_token = request.POST.get('wa_access_token', '').strip()
    message_text = request.POST.get('message', '').strip()
    
    if not wa_phone_number_id or not wa_access_token or not message_text:
        return JsonResponse({'status': 'error', 'error': 'Phone Number ID, Access Token, and message text are required.'}, status=400)
        
    # Find the most recent WhatsApp chat session for this company
    recent_msg = company.messages.filter(visitor_id__startswith='wa_').order_by('-created_at').first()
    if not recent_msg or not recent_msg.visitor_id:
        return JsonResponse({
            'status': 'error',
            'error': 'No recent WhatsApp visitor found in chat logs. Please send a message to your WhatsApp number first to register a recipient.'
        }, status=400)
        
    phone = recent_msg.visitor_id.replace('wa_', '')
    url = f"https://graph.facebook.com/v17.0/{wa_phone_number_id}/messages"
    headers = {
        "Authorization": f"Bearer {wa_access_token}",
        "Content-Type": "application/json"
    }
    payload = {
        "messaging_product": "whatsapp",
        "to": phone,
        "type": "text",
        "text": {"body": message_text}
    }
    
    try:
        response = requests.post(url, headers=headers, json=payload, timeout=10)
        res_data = response.json()
        if response.status_code in [200, 201]:
            # Save the outgoing test message in our database so it displays in chat history
            ChatMessage.objects.create(
                company=company,
                sender='bot',
                content=message_text,
                visitor_id=recent_msg.visitor_id,
                delivery_status='sent'
            )
            return JsonResponse({
                'status': 'success',
                'message': f"Test message pushed successfully to phone: {phone}."
            })
        else:
            err_msg = res_data.get('error', {}).get('message', 'Failed to send message via WhatsApp.')
            return JsonResponse({'status': 'error', 'error': err_msg}, status=400)
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)


@superuser_required
@require_POST
def superuser_retry_ai_reply(request, log_id):
    from .models import WebhookTestLog, Company, ChatMessage
    import json
    import threading

    log = get_object_or_404(WebhookTestLog, pk=log_id)
    if log.source not in ['facebook', 'whatsapp']:
        return JsonResponse({'status': 'error', 'message': 'Only Facebook or WhatsApp webhooks can be retried.'}, status=400)
        
    try:
        req_data = json.loads(request.body)
    except Exception:
        req_data = {}
    
    manual_reply_text = req_data.get('manual_reply_text', '').strip()

    try:
        body = json.loads(log.raw_payload)
        found_message = False
        
        if log.source == 'facebook':
            if body.get('object') == 'page':
                for entry in body.get('entry', []):
                    page_id = entry.get('id')
                    company = Company.objects.filter(fb_page_id=page_id, fb_messenger_enabled=True).first()
                    if not company:
                        continue
                    for messaging_event in entry.get('messaging', []):
                        if messaging_event.get('message') and messaging_event['message'].get('text'):
                            sender_id = messaging_event['sender']['id']
                            message_text = messaging_event['message']['text']
                            meta_message_id = messaging_event['message'].get('mid')
                            
                            if manual_reply_text:
                                log.reply_status = 'replied'
                                log.reply_time_seconds = 0.0
                                log.save()
                                msg = ChatMessage.objects.create(
                                    company=company, sender='bot', content=manual_reply_text,
                                    visitor_id=f"fb_{sender_id}"
                                )
                                from .utils.social_sender import send_outgoing_social_message
                                threading.Thread(
                                    target=send_outgoing_social_message,
                                    args=(msg.id,)
                                ).start()
                            else:
                                log.reply_status = 'pending'
                                log.reply_time_seconds = None
                                log.save()
                                threading.Thread(
                                    target=process_incoming_social_message,
                                    args=(company.id, f"fb_{sender_id}", message_text),
                                    kwargs={
                                        'meta_message_id': meta_message_id,
                                        'webhook_log_id': log.id,
                                    }
                                ).start()
                            found_message = True
                            break
                    if found_message: break

        elif log.source == 'whatsapp':
            if body.get('object') == 'whatsapp_business_account':
                for entry in body.get('entry', []):
                    for change in entry.get('changes', []):
                        value = change.get('value', {})
                        phone_id = value.get('metadata', {}).get('phone_number_id')
                        company = Company.objects.filter(wa_phone_number_id=phone_id, whatsapp_enabled=True).first()
                        if not company:
                            continue
                        if 'messages' in value:
                            for msg in value['messages']:
                                if msg.get('type') == 'text' and msg.get('text'):
                                    sender_phone = msg['from']
                                    message_text = msg['text']['body']
                                    meta_message_id = msg.get('id')
                                    
                                    if manual_reply_text:
                                        log.reply_status = 'replied'
                                        log.reply_time_seconds = 0.0
                                        log.save()
                                        c_msg = ChatMessage.objects.create(
                                            company=company, sender='bot', content=manual_reply_text,
                                            visitor_id=f"wa_{sender_phone}"
                                        )
                                        from .utils.social_sender import send_outgoing_social_message
                                        threading.Thread(
                                            target=send_outgoing_social_message,
                                            args=(c_msg.id,)
                                        ).start()
                                    else:
                                        log.reply_status = 'pending'
                                        log.reply_time_seconds = None
                                        log.save()
                                        threading.Thread(
                                            target=process_incoming_social_message,
                                            args=(company.id, f"wa_{sender_phone}", message_text),
                                            kwargs={
                                                'meta_message_id': meta_message_id,
                                                'webhook_log_id': log.id,
                                            }
                                        ).start()
                                    found_message = True
                                    break
                        if found_message: break

        if found_message:
            return JsonResponse({'status': 'success', 'message': 'AI retry initiated successfully.'})
        else:
            return JsonResponse({'status': 'error', 'message': 'Could not extract valid message payload to retry.'}, status=400)
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)}, status=500)


@superuser_required
def superuser_ai_replies(request):
    from .models import ChatMessage, Company
    companies = Company.objects.all().order_by('-created_at')
    # Pre-fetch recent bot messages for each company
    company_replies = []
    for comp in companies:
        replies = ChatMessage.objects.filter(company=comp, sender='bot').order_by('-created_at')[:10]
        if replies.exists():
            company_replies.append({
                'company': comp,
                'replies': replies
            })
    return render(request, 'superuser/ai_replies.html', {'company_replies': company_replies})


# Start the background queue worker thread automatically on module load
def _start_background_worker():
    import os
    import sys
    import threading
    
    # Avoid starting worker during migrations, unit tests, or statics collection
    if any(cmd in sys.argv for cmd in ['makemigrations', 'migrate', 'collectstatic', 'test']):
        return
        
    # Prevent runserver reloader subprocess double runs
    if 'runserver' in sys.argv and os.environ.get('RUN_MAIN') != 'true':
        return
        
    from .utils.queue_worker import run_queue_worker_loop
    
    def run_worker_thread():
        # Sleep for 5 seconds to ensure Django is fully loaded and stable
        time.sleep(5)
        try:
            run_queue_worker_loop()
        except Exception as e:
            import logging
            logging.getLogger(__name__).exception("Queue worker failed inside thread: %s", e)

    worker_thread = threading.Thread(target=run_worker_thread, name="ChatLabQueueWorker")
    worker_thread.daemon = True
    worker_thread.start()

_start_background_worker()
@login_required
@require_POST
def test_sitemap_view(request):
    try:
        data = json.loads(request.body)
        sitemap_url = data.get('sitemap_url', '').strip()
    except Exception:
        sitemap_url = request.POST.get('sitemap_url', '').strip()
    if not sitemap_url:
        return JsonResponse({'status': 'error', 'message': 'Sitemap URL is required'})
    try:
        import requests
        import xml.etree.ElementTree as ET
        import re
        from bs4 import BeautifulSoup
        headers = {'User-Agent': 'Mozilla/5.0'}
        response = requests.get(sitemap_url, headers=headers, timeout=10)
        response.raise_for_status()
        urls = []
        try:
            root = ET.fromstring(response.content)
            for child in root.iter():
                if 'loc' in child.tag:
                    urls.append(child.text)
        except Exception:
            urls = re.findall(r'<loc>(.*?)</loc>', response.text)
        if not urls:
            return JsonResponse({'status': 'error', 'message': 'No URLs found in the sitemap'})
        product_urls = [u for u in urls if not u.endswith('.xml')][:3]
        if not product_urls:
            sub_sitemap = urls[0]
            sub_resp = requests.get(sub_sitemap, headers=headers, timeout=10)
            try:
                root = ET.fromstring(sub_resp.content)
                product_urls = []
                for child in root.iter():
                    if 'loc' in child.tag:
                        product_urls.append(child.text)
            except Exception:
                product_urls = re.findall(r'<loc>(.*?)</loc>', sub_resp.text)
            product_urls = [u for u in product_urls if not u.endswith('.xml')][:3]
        if not product_urls:
            return JsonResponse({'status': 'error', 'message': 'Found sitemap, but no product URLs found inside.'})
        results = []
        for purl in product_urls:
            try:
                p_resp = requests.get(purl, headers=headers, timeout=5)
                soup = BeautifulSoup(p_resp.text, 'html.parser')
                title = soup.find('meta', property='og:title')
                title = title['content'] if title else (soup.title.string if soup.title else 'No Title')
                price = soup.find('meta', property='product:price:amount')
                price = price['content'] if price else None
                brand = soup.find('meta', property='product:brand')
                brand = brand['content'] if brand else None
                img = soup.find('meta', property='og:image')
                img = img['content'] if img else None
                if not price or not brand:
                    for script in soup.find_all('script', type='application/ld+json'):
                        try:
                            data = json.loads(script.string)
                            if isinstance(data, dict):
                                data = [data]
                            for item in data:
                                if item.get('@type') == 'Product':
                                    if not price and item.get('offers') and isinstance(item.get('offers'), dict):
                                        price = item['offers'].get('price')
                                    if not brand and item.get('brand'):
                                        brand = item['brand'].get('name') if isinstance(item['brand'], dict) else item['brand']
                        except Exception:
                            pass
                results.append({'url': purl, 'title': title, 'price': price or 'Unknown', 'brand': brand or 'Unknown', 'image': img})
            except Exception:
                pass
        return JsonResponse({'status': 'success', 'message': f'Sitemap valid! Found {len(urls)} URLs. Sample parsed:', 'data': results})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': f'Failed to parse sitemap: {str(e)}'})

@require_POST
def test_google_sheet_connection(request, company_id):
    from .models import Company
    from .ai_service import _fetch_google_sheet_csv
    company = get_object_or_404(Company, id=company_id)
    if not request.user.is_superuser and (not hasattr(request.user, 'company') or request.user.company != company):
        return JsonResponse({'status': 'error', 'message': 'Unauthorized'}, status=403)
        
    url = request.POST.get('google_sheet_url')
    if not url:
        return JsonResponse({'status': 'error', 'message': 'Google Sheet URL is required.'})

    try:
        csv_data = _fetch_google_sheet_csv(url)
        if csv_data:
            return JsonResponse({'status': 'success', 'message': 'Successfully fetched data.', 'data': csv_data[:500] + ('...' if len(csv_data) > 500 else '')})
        else:
            return JsonResponse({'status': 'error', 'message': 'Failed to fetch data or sheet is empty.'})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)})

@require_POST
def test_ecom_connection(request, company_id):
    from .models import Company
    company = get_object_or_404(Company, id=company_id)
    if not request.user.is_superuser and (not hasattr(request.user, 'company') or request.user.company != company):
        return JsonResponse({'status': 'error', 'message': 'Unauthorized'}, status=403)
        
    url = request.POST.get('ecom_api_url', '').strip()
    key = request.POST.get('ecom_api_key', '').strip()
    
    if not url or not key:
        return JsonResponse({'status': 'error', 'message': 'URL and API Key are required'})
        
    try:
        if not url.endswith('/'):
            url += '/'
        import requests
        test_url = url + 'api/bot/test/'
        catalog_url = url + 'api/bot/catalog/'
        
        headers = {'Authorization': f'Bearer {key}'}
        
        res = requests.get(test_url, headers=headers, timeout=10)
        if res.status_code != 200:
            return JsonResponse({'status': 'error', 'message': f'Connection failed: HTTP {res.status_code}. Response: {res.text}'})
        
        try:
            test_data = res.json()
            if test_data.get('status') != 'success':
                return JsonResponse({'status': 'error', 'message': f'API Error: {test_data.get("message")}'})
        except:
            return JsonResponse({'status': 'error', 'message': 'Invalid API Endpoint. The server did not return valid JSON. Ensure the custom API is installed on the e-commerce site.'})
            
        cat_res = requests.get(catalog_url, headers=headers, timeout=10)
        data = []
        if cat_res.status_code == 200:
            try:
                j = cat_res.json()
                data = j.get('products', [])
            except:
                return JsonResponse({'status': 'error', 'message': 'Catalog endpoint did not return valid JSON.'})
                
        return JsonResponse({'status': 'success', 'message': 'Connection successful! Data received.', 'data': data})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': f'Exception occurred: {str(e)}'})


def pricing_view(request):
    from .models import SiteSettings
    settings = SiteSettings.objects.first()
    return render(request, 'public/pricing.html', {'settings': settings})

def contact_view(request):
    from .models import SiteSettings
    settings = SiteSettings.objects.first()
    return render(request, 'public/contact.html', {'settings': settings})

@superuser_required
def superuser_site_config(request):
    from .models import SiteSettings
    settings_obj, created = SiteSettings.objects.get_or_create(id=1)
    
    if request.method == 'POST':
        settings_obj.hero_badge_text = request.POST.get('hero_badge_text', '')
        settings_obj.hero_title = request.POST.get('hero_title', '')
        settings_obj.hero_subtitle = request.POST.get('hero_subtitle', '')
        
        settings_obj.home_feature_1_title = request.POST.get('home_feature_1_title', '')
        settings_obj.home_feature_1_desc = request.POST.get('home_feature_1_desc', '')
        settings_obj.home_feature_2_title = request.POST.get('home_feature_2_title', '')
        settings_obj.home_feature_2_desc = request.POST.get('home_feature_2_desc', '')
        settings_obj.home_feature_3_title = request.POST.get('home_feature_3_title', '')
        settings_obj.home_feature_3_desc = request.POST.get('home_feature_3_desc', '')
        
        settings_obj.contact_email = request.POST.get('contact_email', '')
        settings_obj.contact_phone = request.POST.get('contact_phone', '')
        settings_obj.contact_address = request.POST.get('contact_address', '')
        
        settings_obj.pricing_basic_name = request.POST.get('pricing_basic_name', '')
        settings_obj.pricing_basic_price = request.POST.get('pricing_basic_price', '')
        settings_obj.pricing_basic_features = request.POST.get('pricing_basic_features', '')
        
        settings_obj.pricing_pro_name = request.POST.get('pricing_pro_name', '')
        settings_obj.pricing_pro_price = request.POST.get('pricing_pro_price', '')
        settings_obj.pricing_pro_features = request.POST.get('pricing_pro_features', '')
        
        settings_obj.pricing_ent_name = request.POST.get('pricing_ent_name', '')
        settings_obj.pricing_ent_price = request.POST.get('pricing_ent_price', '')
        settings_obj.pricing_ent_features = request.POST.get('pricing_ent_features', '')
        
        if request.FILES.get('logo'):
            settings_obj.logo = request.FILES['logo']
            
        settings_obj.save()
        messages.success(request, 'Public site configuration updated successfully.')
        return redirect('superuser_site_config')
        
    return render(request, 'superuser/site_config.html', {'settings': settings_obj})


@csrf_exempt
@require_POST
def submit_lead(request):
    import json
    from .models import Lead
    
    # Handle JSON or standard form-encoded data
    if request.content_type == 'application/json':
        try:
            data = json.loads(request.body)
        except Exception:
            data = {}
    else:
        data = request.POST

    name = data.get('name', '').strip()
    phone = data.get('phone', '').strip()
    page_url = data.get('page_url', '').strip()

    if not name or not phone:
        return JsonResponse({'status': 'error', 'error': 'Name and Phone Number are required.'}, status=400)

    Lead.objects.create(
        name=name,
        phone=phone,
        page_url=page_url or None
    )
    return JsonResponse({'status': 'success', 'message': 'Lead details saved successfully!'})


@superuser_required
def superuser_delete_lead(request, pk):
    from .models import Lead
    lead = get_object_or_404(Lead, pk=pk)
    lead.delete()
    messages.success(request, 'Lead removed successfully.')
    return redirect('superuser_leads_list')


@superuser_required
def superuser_leads_list(request):
    from .models import Lead
    leads = Lead.objects.all().order_by('-created_at')
    
    if request.method == 'POST' and request.POST.get('action') == 'clear_all':
        Lead.objects.all().delete()
        messages.success(request, 'All captured leads cleared successfully.')
        return redirect('superuser_leads_list')
        
    return render(request, 'superuser/leads_list.html', {'leads': leads})


def pwa_manifest_view(request):
    from .models import SiteSettings
    settings = SiteSettings.objects.first()
    logo_url = None
    if settings and settings.logo:
        logo_url = request.build_absolute_uri(settings.logo.url)
    else:
        # Fallback default logo using Django static storage helper
        from django.templatetags.static import static
        logo_url = request.build_absolute_uri(static('chatapp/images/logo.png'))
        
    manifest = {
        "name": "ChatLab - AI Sales Agent",
        "short_name": "ChatLab",
        "start_url": "/",
        "display": "standalone",
        "background_color": "#000000",
        "theme_color": "#6366f1",
        "icons": [
            {
                "src": logo_url,
                "sizes": "192x192 512x512",
                "type": "image/png"
            }
        ]
    }
    return JsonResponse(manifest)


def pwa_service_worker_view(request):
    from django.http import HttpResponse
    js_content = """
    self.addEventListener('install', function(event) {
        event.waitUntil(self.skipWaiting());
    });
    self.addEventListener('activate', function(event) {
        event.waitUntil(self.clients.claim());
    });
    self.addEventListener('fetch', function(event) {
        event.respondWith(fetch(event.request));
    });
    """
    return HttpResponse(js_content, content_type="application/javascript")


def serve_static_fallback(request, path):
    from django.contrib.staticfiles import finders
    from django.http import Http404, HttpResponse
    import mimetypes
    import os
    
    normalized_path = os.path.normpath(path).replace('\\', '/')
    result = finders.find(normalized_path)
    if result:
        if isinstance(result, (list, tuple)):
            file_path = result[0]
        else:
            file_path = result
            
        with open(file_path, 'rb') as f:
            content = f.read()
        content_type, _ = mimetypes.guess_type(file_path)
        return HttpResponse(content, content_type=content_type or 'application/octet-stream')
    raise Http404("Static file not found")


def serve_media_fallback(request, path):
    from django.views.static import serve
    from django.conf import settings
    return serve(request, path, document_root=settings.MEDIA_ROOT)


@login_required
def agent_facebook_comments(request):
    from .models import FacebookComment
    user = request.user
    if user.role not in ['AGENT', 'ADMIN', 'SUPERUSER']:
        return HttpResponseForbidden("Unauthorized")
    
    comments = FacebookComment.objects.filter(company=user.company)
    
    return render(request, 'agent/facebook_comments.html', {'comments': comments})

@login_required
@require_POST
def agent_reply_facebook_comment(request, comment_id):
    from .models import FacebookComment
    import requests as req_lib
    
    user = request.user
    if user.role not in ['AGENT', 'ADMIN', 'SUPERUSER']:
        return HttpResponseForbidden("Unauthorized")
        
    comment = get_object_or_404(FacebookComment, id=comment_id, company=user.company)
    reply_message = request.POST.get('message', '').strip()
    
    if not reply_message:
        messages.error(request, 'Reply message cannot be empty.')
        return redirect('agent_facebook_comments')
        
    url = f"https://graph.facebook.com/v17.0/{comment.comment_id}/comments"
    payload = {
        "message": reply_message,
        "access_token": user.company.fb_page_access_token
    }
    
    try:
        response = req_lib.post(url, json=payload, timeout=10)
        if response.status_code == 200:
            from django.utils import timezone as _tz
            comment.is_handled = True
            comment.replied_at = _tz.now()
            comment.save(update_fields=['is_handled', 'replied_at'])
            messages.success(request, 'Successfully replied to the comment.')
        else:
            err = response.json().get('error', {}).get('message', 'Unknown error')
            messages.error(request, f'Failed to reply: {err}')
    except Exception as e:
        messages.error(request, f'Error: {str(e)}')
        
    return redirect('agent_facebook_comments')



@agent_or_admin_required
def agent_search_suggestions(request):
    from django.db.models import Q
    from django.http import JsonResponse
    from .models import Escalation, StoreComplaint
    
    company = request.user.company
    q = request.GET.get('q', '').strip()
    results = []
    
    if len(q) >= 1:
        # Search Escalations (conversations) by visitor_id
        escalations = Escalation.objects.filter(
            company=company,
            visitor_id__icontains=q
        ).order_by('-updated_at')[:10]
        
        for esc in escalations:
            results.append({
                'type': 'visitor',
                'id': esc.visitor_id,
                'label': f"Chat: {esc.visitor_id}",
                'url': f"/agent/chat/{esc.visitor_id}/"
            })
            
        # Search Complaints by ID (pk), title, or customer_name
        complaints_query = Q(company=company)
        if q.isdigit():
            complaints_query &= Q(id=int(q)) | Q(title__icontains=q) | Q(customer_name__icontains=q)
        else:
            complaints_query &= Q(title__icontains=q) | Q(customer_name__icontains=q)
            
        complaints = StoreComplaint.objects.filter(complaints_query).order_by('-created_at')[:10]
        for c in complaints:
            results.append({
                'type': 'complaint',
                'id': str(c.id),
                'label': f"Complaint #{c.id}: {c.title}",
                'url': "/agent/complaints/"
            })
            
    return JsonResponse({'status': 'success', 'results': results})


# ----------------- ADMIN MESSAGES CONSOLE VIEWS -----------------

@admin_required
def admin_messages_dashboard(request):
    company = get_admin_company(request)
    claimed = list(company.escalations.filter(claimed_by__isnull=False).order_by('-updated_at'))
    pending = list(company.escalations.filter(claimed_by__isnull=True).order_by('-updated_at'))
    
    # Resolve Facebook visitor names for escalations using cache
    for esc in claimed + pending:
        if esc.visitor_id and esc.visitor_id.startswith('fb_'):
            prof = get_visitor_profile(esc.visitor_id, company)
            esc.fb_name = prof['name']
        else:
            esc.fb_name = None
        esc.fb_pic = None
        
        # Add claimed_by_name helper attribute
        if esc.claimed_by:
            if esc.claimed_by == request.user:
                esc.claimed_by_name = "You"
            else:
                esc.claimed_by_name = esc.claimed_by.agent_name or esc.claimed_by.username
        else:
            esc.claimed_by_name = None
        
    return render(request, 'admin/messages_dashboard.html', {
        'company': company,
        'claimed': claimed,
        'pending': pending,
    })


@admin_required
def admin_chat(request, visitor_id):
    """Chat panel for a specific visitor."""
    company = get_admin_company(request)
    claim = company.escalations.filter(visitor_id=visitor_id).order_by('-created_at').first()
    if not claim:
        claim = Escalation.objects.create(company=company, message="Admin started chat", visitor_id=visitor_id)
        
    msgs = company.messages.filter(visitor_id=visitor_id).order_by('id')
    last_msg = msgs.last()
    last_id = last_msg.id if last_msg else 0
    quick_types = company.quick_types.all()

    visitor_name = visitor_id
    visitor_profile_pic = None
    if visitor_id.startswith('fb_') and company.fb_page_access_token:
        psid = visitor_id.replace('fb_', '')
        try:
            import requests as req_lib
            url = f"https://graph.facebook.com/{psid}?fields=first_name,last_name,profile_pic&access_token={company.fb_page_access_token}"
            resp = req_lib.get(url, timeout=3)
            if resp.status_code == 200:
                data = resp.json()
                first_name = data.get('first_name', '')
                last_name = data.get('last_name', '')
                if first_name or last_name:
                    visitor_name = f"{first_name} {last_name}".strip()
                visitor_profile_pic = data.get('profile_pic')
        except Exception:
            pass

    return render(request, 'admin/chat.html', {
        'company': company,
        'visitor_id': visitor_id,
        'visitor_name': visitor_name,
        'visitor_profile_pic': visitor_profile_pic,
        'messages': msgs,
        'escalation': claim,
        'last_id': last_id,
        'quick_types': quick_types,
    })


@admin_required
@require_POST
def admin_claim_escalation_to_chat(request, pk):
    company = get_admin_company(request)
    esc = get_object_or_404(Escalation, pk=pk, company=company)
    esc.claimed_by = request.user
    esc.claimed_at = timezone.now()
    esc.is_handled = True
    esc.save()
    try:
        ChatMessage.objects.create(company=company, sender='system', content=f"Admin {request.user.username} joined.", visitor_id=esc.visitor_id, escalation=esc)
    except Exception:
        pass
    return redirect('admin_chat', visitor_id=esc.visitor_id)


@admin_required
def admin_pending_escalations(request):
    """Poll for new pending and active escalations for the admin messages dashboard."""
    company = get_admin_company(request)
    pending = company.escalations.filter(claimed_by__isnull=True).order_by('-updated_at').values(
        'id', 'message', 'visitor_id', 'created_at', 'updated_at'
    )[:10]
    
    active = company.escalations.filter(claimed_by__isnull=False).order_by('-updated_at').values(
        'id', 'message', 'visitor_id', 'created_at', 'updated_at', 'claimed_at', 'ai_mode_active',
        'claimed_by__username', 'claimed_by__agent_name'
    )[:20]

    out_pending = []
    for esc in pending:
        prof = get_visitor_profile(esc['visitor_id'], company)
        out_pending.append({
            'id': esc['id'],
            'message': esc['message'][:100],
            'visitor_id': esc['visitor_id'],
            'visitor_name': prof['name'],
            'visitor_profile_pic': prof['pic'],
            'created_at': esc['created_at'].isoformat(),
            'updated_at': esc['updated_at'].isoformat(),
        })
        
    out_active = []
    for esc in active:
        prof = get_visitor_profile(esc['visitor_id'], company)
        claimed_by_name = esc.get('claimed_by__agent_name') or esc.get('claimed_by__username') or "Agent"
        if esc.get('claimed_by__username') == request.user.username:
            claimed_by_name = "You"
        out_active.append({
            'id': esc['id'],
            'message': esc['message'][:100],
            'visitor_id': esc['visitor_id'],
            'visitor_name': prof['name'],
            'visitor_profile_pic': prof['pic'],
            'created_at': esc['created_at'].isoformat(),
            'updated_at': esc['updated_at'].isoformat(),
            'claimed_at': esc['claimed_at'].isoformat() if esc['claimed_at'] else None,
            'ai_mode_active': esc['ai_mode_active'],
            'claimed_by_name': claimed_by_name
        })
        
    return JsonResponse({'status': 'success', 'escalations': out_pending, 'active': out_active})


@admin_required
@require_POST
def admin_chat_send_message(request, visitor_id):
    """Admin posts a message to a visitor."""
    company = get_admin_company(request)
    claim = company.escalations.filter(visitor_id=visitor_id).order_by('-created_at').first()
    if not claim:
        claim = Escalation.objects.create(company=company, message="Admin started chat", visitor_id=visitor_id)
        
    # Admin does not claim the conversation; they just reply.
    # We leave claim.claimed_by as it is.
    message = request.POST.get('message', '').strip()
    attachments = request.FILES.getlist('attachments')
    
    for att in attachments:
        if att.size > 2 * 1024 * 1024:
            return JsonResponse({'status': 'error', 'error': f'File {att.name} exceeds the 2MB limit.'}, status=400)
    
    if not message and not attachments:
        return JsonResponse({'status': 'error', 'error': 'Message or attachment required.'}, status=400)
        
    try:
        created_messages = []
        
        # 1. Save text message first
        if message:
            msg = ChatMessage.objects.create(
                company=company,
                sender='agent',
                content=message,
                visitor_id=visitor_id,
                message_type='text',
                escalation=claim,
            )
            created_messages.append(msg)
            
        # 2. Save attachments
        for att in attachments:
            ctype = att.content_type
            msg_type = 'image' if ctype.startswith('image/') else ('voice' if ctype.startswith('audio/') else 'file')
            msg = ChatMessage.objects.create(
                company=company,
                sender='agent',
                content=f'[{msg_type.upper()}]',
                visitor_id=visitor_id,
                message_type=msg_type,
                attachment=att,
                escalation=claim,
            )
            created_messages.append(msg)
        
        # Send to Meta API if visitor is social
        if visitor_id and (visitor_id.startswith('fb_') or visitor_id.startswith('wa_')):
            from .utils.social_sender import send_outgoing_social_message
            for m in created_messages:
                threading.Thread(
                    target=send_outgoing_social_message,
                    args=(m.id,)
                ).start()
                
        # Format date for response
        response_msgs = []
        for m in created_messages:
            date_str = m.created_at.isoformat()
            if date_str.endswith('+00:00'):
                date_str = date_str[:-6] + 'Z'
            response_msgs.append({
                'id': m.id,
                'sender': m.sender,
                'content': m.content,
                'message_type': m.message_type,
                'attachment': m.attachment.url if m.attachment else None,
                'created_at': date_str,
            })
            
        return JsonResponse({'status': 'success', 'messages': response_msgs})
    except Exception as e:
        return JsonResponse({'status': 'error', 'error': str(e)}, status=500)


@admin_required
@require_POST
def admin_chat_update_typing_status(request, visitor_id):
    """Admin indicates they are typing."""
    company = get_admin_company(request)
    claim = company.escalations.filter(visitor_id=visitor_id).order_by('-created_at').first()
    if not claim:
        return JsonResponse({'status': 'error', 'error': 'Conversation not found.'}, status=404)
    claim.agent_last_typing_at = timezone.now()
    claim.save(update_fields=['agent_last_typing_at'])
    return JsonResponse({'status': 'success'})


@admin_required
@require_POST
def admin_chat_release_escalation(request, visitor_id):
    """Release a conversation back to the queue (unclaim)."""
    company = get_admin_company(request)
    claim = company.escalations.filter(visitor_id=visitor_id, is_handled=True).order_by('-created_at').first()
    if not claim:
        messages.error(request, "Conversation not found.")
        return redirect('admin_messages_dashboard')
        
    claim.claimed_by = None
    claim.claimed_at = None
    claim.is_handled = False
    claim.save()
    
    try:
        ChatMessage.objects.create(company=company, sender='system', content=f"Admin {request.user.username} left.", visitor_id=visitor_id, escalation=claim)
    except Exception:
        pass
        
    messages.success(request, "Conversation released back to queue.")
    return redirect('admin_messages_dashboard')


@admin_required
def admin_chat_poll_messages(request, visitor_id):
    """Long-poll endpoint for the admin chat panel: returns new messages after since_id."""
    company = get_admin_company(request)
    claim = company.escalations.filter(visitor_id=visitor_id).order_by('-created_at').first()
    if not claim:
        return JsonResponse({'status': 'error', 'error': 'Conversation not found.'}, status=404)
    since_id = int(request.GET.get('since_id') or 0)
    msgs = company.messages.filter(visitor_id=visitor_id, id__gt=since_id).order_by('id')
    out = []
    for m in msgs:
        msg_data = {
            'id': m.id,
            'sender': m.sender,
            'content': m.content,
            'message_type': m.message_type,
            'created_at': m.created_at.isoformat(),
        }
        if m.attachment:
            msg_data['attachment'] = m.attachment.url
        elif m.attachment_url:
            msg_data['attachment'] = m.attachment_url
        out.append(msg_data)
    return JsonResponse({'status': 'success', 'messages': out})


@admin_required
@require_POST
def admin_chat_toggle_ai_mode(request, visitor_id):
    """Toggle AI auto-response mode for a conversation by admin."""
    company = get_admin_company(request)
    claim = company.escalations.filter(visitor_id=visitor_id).order_by('-created_at').first()
    if not claim:
        messages.error(request, "No escalation found for this visitor.")
        return redirect('admin_messages_dashboard')
        
    if not getattr(company, 'ai_agent_active', True) and not claim.ai_mode_active:
        messages.error(request, "AI Agent mode is turned off by Admin.")
        return redirect('admin_chat', visitor_id=visitor_id)

    claim.ai_mode_active = not claim.ai_mode_active
    claim.save(update_fields=['ai_mode_active'])
    status_str = "activated" if claim.ai_mode_active else "deactivated"
    messages.success(request, f"AI Mode {status_str} for this conversation.")
    return redirect('admin_chat', visitor_id=visitor_id)


@admin_required
@require_POST
def admin_chat_edit_message(request, message_id):
    company = get_admin_company(request)
    msg = get_object_or_404(ChatMessage, id=message_id, company=company, sender='agent')
    content = request.POST.get('content', '').strip()
    if not content:
        return JsonResponse({'status': 'error', 'error': 'Content cannot be empty.'}, status=400)
    msg.content = content
    msg.save()
    return JsonResponse({'status': 'success', 'message': {'id': msg.id, 'content': msg.content}})


@admin_required
@require_POST
def admin_chat_delete_message(request, message_id):
    company = get_admin_company(request)
    msg = get_object_or_404(ChatMessage, id=message_id, company=company, sender='agent')
    msg.delete()
    return JsonResponse({'status': 'success'})


# ----------------- VOICE REPLY CRUD VIEWS -----------------

@admin_required
def admin_voice_reply_list(request):
    company = get_admin_company(request)
    replies = company.voice_replies.all().order_by('-created_at')
    return render(request, 'admin/voice_reply_list.html', {
        'company': company,
        'replies': replies,
    })


@admin_required
def admin_voice_reply_create(request):
    company = get_admin_company(request)
    if request.method == 'POST':
        keyword = request.POST.get('keyword', '').strip()
        audio_file = request.FILES.get('audio_file')
        if not keyword:
            messages.error(request, 'Keyword is required.')
        elif not audio_file:
            messages.error(request, 'Audio file is required.')
        else:
            VoiceReply.objects.create(
                company=company,
                keyword=keyword,
                audio_file=audio_file,
            )
            messages.success(request, 'Voice reply created successfully.')
            return redirect('admin_voice_reply_list')

    return render(request, 'admin/voice_reply_form.html', {
        'company': company,
        'object': None,
    })


@admin_required
def admin_voice_reply_edit(request, pk):
    company = get_admin_company(request)
    reply = get_object_or_404(VoiceReply, pk=pk, company=company)
    if request.method == 'POST':
        keyword = request.POST.get('keyword', '').strip()
        audio_file = request.FILES.get('audio_file')
        if not keyword:
            messages.error(request, 'Keyword is required.')
        else:
            reply.keyword = keyword
            if audio_file:
                reply.audio_file = audio_file
            reply.save()
            messages.success(request, 'Voice reply updated successfully.')
            return redirect('admin_voice_reply_list')

    return render(request, 'admin/voice_reply_form.html', {
        'company': company,
        'object': reply,
    })


@admin_required
@require_POST
def admin_voice_reply_delete(request, pk):
    company = get_admin_company(request)
    reply = get_object_or_404(VoiceReply, pk=pk, company=company)
    reply.delete()
    messages.success(request, 'Voice reply deleted successfully.')
    return redirect('admin_voice_reply_list')


