"""
AI Chat — per-tenant chat sessions powered by OpenAI GPT-4o mini.

Session lifecycle:
1. Visitor opens widget → ChatSession created with unique session_id
2. Visitor sends message → ChatMessage(role=user) stored
3. OpenAI responds → ChatMessage(role=assistant) stored
4. If intent keywords detected → WhatsApp handoff offered
5. Session archived after 24h inactivity
"""
import uuid
from django.db import models


STOPWORDS = {
    'a', 'about', 'after', 'again', 'all', 'also', 'am', 'an', 'and', 'any', 'are',
    'as', 'at', 'be', 'been', 'but', 'by', 'can', 'could', 'did', 'do', 'does',
    'doing', 'for', 'from', 'get', 'give', 'good', 'had', 'has', 'have', 'hello',
    'help', 'hey', 'hi', 'how', 'i', 'if', 'in', 'is', 'it', 'just', 'know', 'like',
    'looking', 'make', 'me', 'more', 'my', 'need', 'no', 'not', 'of', 'ok', 'on',
    'one', 'or', 'our', 'out', 'please', 'so', 'some', 'thanks', 'that', 'the',
    'their', 'them', 'then', 'there', 'these', 'they', 'this', 'to', 'up', 'us',
    'want', 'was', 'we', 'well', 'what', 'when', 'where', 'which', 'who', 'why',
    'will', 'with', 'would', 'yes', 'you', 'your', 'much', 'many', 'per', 'still',
    'now', 'here', 'over', 'into', 'than', 'very', 'too', 'let', 'lets', 'tell',
    'sure', 'okay', 'thank', 'because', 'been', 'were', 'may', 'might', 'should',
}


# The widget's quick reply chips send a sentence rather than their own label,
# so map those sentences back to the option the visitor actually clicked.
# Keys must stay in step with QUICK_REPLIES in templates/chat/widget.html.
QUICK_REPLY_TOPICS = {
    'premium domains':        'Premium Domains',
    'web design project':     'Web Design',
    'app design services':    'App Design',
    'startup solutions':      'Startup Solutions',
    'digital marketing':      'Digital Marketing',
    'seo and sem':            'SEO and SEM',
    'ecommerce store':        'Ecommerce Store',
    'hosting and maintenance': 'Hosting and Support',
    'technical consultation': 'Technical Consultation',
    'something else':         'Other Enquiry',
}


def quick_reply_label(messages):
    """Return the chip label if a visitor message came from a quick reply."""
    for text in messages:
        low = (text or '').lower()
        for phrase, label in QUICK_REPLY_TOPICS.items():
            if phrase in low:
                return label
    return ''


def keyword_label(messages):
    """
    Build a two word label without calling any API.

    Used when the AI label is unavailable, for example when the OpenAI
    account has no credits. Picks the most repeated meaningful words, and
    falls back to General Enquiry when a message is only a greeting.
    """
    import re
    from collections import Counter

    counts = Counter()
    order = {}
    for text in messages:
        for pos, word in enumerate(re.findall(r"[A-Za-z][A-Za-z'-]{2,}", text or '')):
            low = word.lower()
            if low in STOPWORDS:
                continue
            counts[low] += 1
            order.setdefault(low, pos)

    if not counts:
        return 'General Enquiry'

    ranked = sorted(counts.items(), key=lambda kv: (-kv[1], order[kv[0]]))
    picked = [w for w, _ in ranked[:2]]
    if len(picked) == 1:
        picked.append('Enquiry')
    return ' '.join(w.capitalize() for w in picked)


class ChatSession(models.Model):
    STATUS_ACTIVE    = 'active'
    STATUS_HANDOFF   = 'handoff'   # WhatsApp handoff offered/accepted
    STATUS_CLOSED    = 'closed'

    STATUS_CHOICES = [
        (STATUS_ACTIVE,  'Active'),
        (STATUS_HANDOFF, 'WhatsApp Handoff'),
        (STATUS_CLOSED,  'Closed'),
    ]

    tenant      = models.ForeignKey(
        'tenants.Tenant', on_delete=models.CASCADE,
        related_name='chat_sessions'
    )
    session_id  = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
    status      = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_ACTIVE)

    # Visitor info (filled in if they identify themselves)
    visitor_name  = models.CharField(max_length=150, blank=True)
    visitor_email = models.EmailField(blank=True)
    visitor_ip    = models.GenericIPAddressField(null=True, blank=True)

    # Two word topic label, generated once from the opening messages so the
    # CMS can show what a chat is about instead of a wall of "Anonymous".
    topic = models.CharField(max_length=60, blank=True)

    # Handoff tracking
    handoff_triggered = models.BooleanField(default=False)
    handoff_keyword   = models.CharField(max_length=100, blank=True)
    handoff_at        = models.DateTimeField(null=True, blank=True)

    # If a ticket was created from this session
    ticket = models.OneToOneField(
        'tickets.Ticket', on_delete=models.SET_NULL, null=True, blank=True,
        related_name='chat_session'
    )

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']
        verbose_name = 'Chat Session'
        verbose_name_plural = 'Chat Sessions'

    def __str__(self):
        return f'Chat {self.session_id.hex[:8]} — {self.tenant.name} — {self.display_label}'

    @property
    def reference(self):
        """Short code shown in the CMS and prefilled into the WhatsApp message.

        Derived rather than stored, so it is built from the primary key rather
        than counted. A chat is short lived and nobody quotes one back a month
        later, which is why this does not need its own sequence.
        """
        from apps.tenants.refs import stem, CHAT
        when = getattr(self, 'started_at', None) or getattr(self, 'created_at', None)
        return f'{stem(CHAT, getattr(self, "tenant", None), when)}{self.pk:04d}'

    @property
    def whatsapp_handoff_url(self):
        """
        wa.me link with a prefilled first message carrying the reference and
        topic. WhatsApp still hides the visitor's number from us, but the code
        arriving in the inbox identifies which website chat they came from.
        """
        base = getattr(self.tenant, 'whatsapp_link', '') or ''
        if not base:
            return ''
        from urllib.parse import quote
        subject = self.topic or 'an enquiry'
        text = f'Hi, I was chatting on your website about {subject}. Ref {self.reference}'
        return f'{base}?text={quote(text)}'

    @property
    def display_label(self):
        """Visitor name if given, otherwise the topic, otherwise Anonymous."""
        return self.visitor_name or self.topic or 'Anonymous'

    @property
    def message_count(self):
        return self.messages.count()

    def generate_topic(self, max_messages=4):
        """
        Ask the model for a two word label describing what this chat is about.

        Runs once per session and stores the result. Any failure leaves the
        field blank so callers fall back to Anonymous. Never raises.
        """
        if self.topic:
            return self.topic

        said = list(
            self.messages.filter(role=ChatMessage.ROLE_USER)
            .order_by('created_at')
            .values_list('content', flat=True)[:max_messages]
        )
        if not said:
            return ''

        # A clicked option is already the clearest possible label, so use it
        # directly and skip the API call entirely.
        chip = quick_reply_label(said)
        if chip:
            self.topic = chip[:60]
            self.save(update_fields=['topic'])
            return self.topic

        prompt = (
            'Summarise what this website visitor is asking about in exactly two words, '
            'in title case, no punctuation, no quotes. Examples: Hosting Pricing, '
            'Domain Transfer, Website Redesign, Payment Problem. '
            'If the messages are only a greeting with no subject, reply with: General Enquiry'
            + chr(10) + chr(10)
            + chr(10).join(m[:400] for m in said)
        )

        try:
            from apps.chat.openai_client import get_short_label
            label = get_short_label(prompt, self.tenant)
        except Exception:
            label = ''

        if not label:
            label = keyword_label(said)

        if label:
            self.topic = label[:60]
            self.save(update_fields=['topic'])
        return self.topic

    def build_openai_history(self, max_messages=20):
        """
        Returns the last N messages formatted for the OpenAI messages array.
        Prepends the tenant system prompt, then injects a second system
        message with live DB data (products, FAQs, domain listings).
        """
        system_prompt = self.tenant.chat_system_prompt or (
            f'You are a helpful assistant for {self.tenant.name}. '
            f'Be concise, friendly, and professional. '
            f'If the user asks about pricing, sales, or wants to make a purchase, '
            f'let them know you can connect them with the team directly via WhatsApp.'
        )

        history = [{'role': 'system', 'content': system_prompt}]

        # Inject live DB context as a second system message
        try:
            from apps.chat.context_builder import build_context_block
            context_block = build_context_block(self.tenant)
            if context_block:
                history.append({'role': 'system', 'content': context_block})
        except Exception:
            pass  # Never break chat due to context injection errors

        messages = self.messages.order_by('-created_at')[:max_messages]
        for msg in reversed(list(messages)):
            history.append({'role': msg.role, 'content': msg.content})
        return history


class ChatMessage(models.Model):
    ROLE_USER      = 'user'
    ROLE_ASSISTANT = 'assistant'
    ROLE_SYSTEM    = 'system'

    ROLE_CHOICES = [
        (ROLE_USER,      'User'),
        (ROLE_ASSISTANT, 'Assistant'),
        (ROLE_SYSTEM,    'System'),
    ]

    session    = models.ForeignKey(ChatSession, on_delete=models.CASCADE, related_name='messages')
    role       = models.CharField(max_length=20, choices=ROLE_CHOICES)
    content    = models.TextField()
    tokens     = models.PositiveIntegerField(default=0, help_text='OpenAI tokens used for this message')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['created_at']
        verbose_name = 'Chat Message'
        verbose_name_plural = 'Chat Messages'

    def __str__(self):
        return f'[{self.role}] {self.content[:60]}'
