# analytics/services.py
"""
Insight Engine — generates actionable business insights per company.
All queries are scoped to the company for full tenant isolation.
"""
import logging
from datetime import timedelta, date
from decimal import Decimal

from django.utils import timezone
from django.db.models import Sum, Count, Avg, Q, F, Max
from django.db.models.functions import TruncHour, TruncDate

logger = logging.getLogger(__name__)


class InsightEngine:
    """
    Generates insights for a single company.
    Call generate_all() to run all insight categories.
    """

    def __init__(self, company):
        self.company = company
        self.today = timezone.now().date()
        self.now = timezone.now()

    def _create_insight(self, title, description, category, priority='medium', color='blue', icon='fa-lightbulb'):
        """Create or update a today-scoped insight (deduplication by title+company+date)."""
        from analytics.models import Insight
        # Deduplicate: one insight of same title per company per day
        obj, created = Insight.objects.get_or_create(
            company=self.company,
            title=title,
            created_at__date=self.today,
            defaults={
                'description': description,
                'category': category,
                'priority': priority,
                'color': color,
                'icon': icon,
            }
        )
        if not created and obj.description != description:
            obj.description = description
            obj.save(update_fields=['description'])
        return obj

    def _fmt(self, amount):
        """Format currency with company symbol."""
        cs = getattr(self.company, 'settings', None)
        sym = cs.currency_symbol if cs else '₦'
        use_comma = getattr(cs, 'use_comma_separator', True) if cs else True
        if use_comma:
            return f"{sym}{amount:,.2f}"
        return f"{sym}{amount:.2f}"

    # ─── SALES INSIGHTS ───────────────────────────────────────────────────────

    def _sales_insights(self):
        from sales.models import POSOrder
        today = self.today
        yesterday = today - timedelta(days=1)

        def day_revenue(d):
            return POSOrder.objects.filter(
                company=self.company, created_at__date=d, status='completed'
            ).aggregate(t=Sum('final_amount'))['t'] or Decimal('0')

        today_rev = day_revenue(today)
        yesterday_rev = day_revenue(yesterday)

        # 1. Daily revenue change
        if yesterday_rev > 0:
            pct = ((today_rev - yesterday_rev) / yesterday_rev) * 100
            if pct >= 0:
                self._create_insight(
                    title=f"Revenue up {pct:.0f}% today",
                    description=f"Your revenue today ({self._fmt(today_rev)}) increased by {pct:.1f}% compared to yesterday ({self._fmt(yesterday_rev)}).",
                    category='sales', priority='low', color='green', icon='fa-arrow-trend-up'
                )
            else:
                self._create_insight(
                    title=f"Revenue down {abs(pct):.0f}% today",
                    description=f"Sales dropped by {abs(pct):.1f}% compared to yesterday ({self._fmt(yesterday_rev)} → {self._fmt(today_rev)}).",
                    category='sales', priority='medium', color='yellow', icon='fa-arrow-trend-down'
                )

        # 2. Weekly performance
        last_week_rev = sum(
            day_revenue(today - timedelta(days=i)) for i in range(8, 15)
        )
        this_week_rev = sum(
            day_revenue(today - timedelta(days=i)) for i in range(1, 8)
        )
        if last_week_rev > 0:
            w_pct = ((this_week_rev - last_week_rev) / last_week_rev) * 100
            color = 'green' if w_pct >= 0 else 'yellow'
            direction = 'up' if w_pct >= 0 else 'down'
            self._create_insight(
                title=f"Weekly revenue {direction} {abs(w_pct):.0f}%",
                description=f"This week's revenue ({self._fmt(this_week_rev)}) is {abs(w_pct):.1f}% {'higher' if w_pct >= 0 else 'lower'} than last week ({self._fmt(last_week_rev)}).",
                category='sales', priority='medium', color=color, icon='fa-chart-bar'
            )

        # 3. Slow sales warning (today < 50% of 7-day avg)
        avg_7 = sum(day_revenue(today - timedelta(days=i)) for i in range(1, 8)) / 7
        if avg_7 > 0 and today_rev < avg_7 * Decimal('0.5'):
            drop = ((avg_7 - today_rev) / avg_7) * 100
            self._create_insight(
                title="Slow sales day",
                description=f"Sales today ({self._fmt(today_rev)}) are {drop:.0f}% below your 7-day average ({self._fmt(avg_7)}). Consider a promotion.",
                category='sales', priority='high', color='red', icon='fa-exclamation-triangle'
            )

        # 4. Peak day of week
        from django.db.models.functions import ExtractWeekDay
        peak = (
            POSOrder.objects.filter(
                company=self.company, status='completed',
                created_at__date__gte=today - timedelta(days=90)
            )
            .annotate(weekday=ExtractWeekDay('created_at'))
            .values('weekday')
            .annotate(total=Sum('final_amount'))
            .order_by('-total')
            .first()
        )
        if peak:
            days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
            day_name = days[peak['weekday'] - 1] if 1 <= peak['weekday'] <= 7 else 'Unknown'
            self._create_insight(
                title=f"Best sales day: {day_name}",
                description=f"Based on the last 90 days, {day_name} generates the most revenue. Consider scheduling extra staff or promotions on {day_name}s.",
                category='sales', priority='low', color='blue', icon='fa-calendar-star'
            )

        # 5. Best sales hour (last 30 days)
        from django.db.models.functions import ExtractHour
        peak_hour = (
            POSOrder.objects.filter(
                company=self.company, status='completed',
                created_at__gte=self.now - timedelta(days=30)
            )
            .annotate(hr=ExtractHour('created_at'))
            .values('hr')
            .annotate(cnt=Count('id'))
            .order_by('-cnt')
            .first()
        )
        if peak_hour:
            hr = peak_hour['hr']
            end_hr = (hr + 2) % 24
            self._create_insight(
                title=f"Peak sales hour: {hr:02d}:00–{end_hr:02d}:00",
                description=f"Most of your sales happen around {hr:02d}:00. Make sure you have enough staff and stock during this window.",
                category='sales', priority='low', color='blue', icon='fa-clock'
            )

    # ─── PRODUCT INSIGHTS ─────────────────────────────────────────────────────

    def _product_insights(self):
        from sales.models import POSOrderItem
        from inventory.models import Product

        since_7 = self.now - timedelta(days=7)
        since_30 = self.now - timedelta(days=30)
        since_45 = self.now - timedelta(days=45)

        # Top selling (7 days)
        top5 = (
            POSOrderItem.objects.filter(
                order__company=self.company, order__status='completed',
                order__created_at__gte=since_7
            )
            .values('product__name')
            .annotate(total_qty=Sum('quantity'), total_rev=Sum('total_price'))
            .order_by('-total_qty')[:5]
        )
        if top5:
            top_name = top5[0]['product__name']
            self._create_insight(
                title=f"Top seller this week: {top_name}",
                description=f"{top_name} is your most sold product this week ({top5[0]['total_qty']} units). "
                            f"Top 5: {', '.join(r['product__name'] for r in top5 if r['product__name'])}.",
                category='inventory', priority='low', color='green', icon='fa-star'
            )

        # Dead stock (no sales in 45 days, but has stock)
        sold_ids = POSOrderItem.objects.filter(
            order__company=self.company, order__status='completed',
            order__created_at__gte=self.now - timedelta(days=45)
        ).values_list('product_id', flat=True).distinct()

        dead = Product.objects.filter(
            company=self.company, status='active', stock_quantity__gt=0
        ).exclude(id__in=sold_ids)[:10]

        if dead.exists():
            dead_names = ', '.join(d.name for d in dead[:5])
            self._create_insight(
                title=f"{dead.count()} dead stock product(s)",
                description=f"These products have not sold in 45+ days but still have stock: {dead_names}. Consider discounting or a clearance sale.",
                category='inventory', priority='high', color='red', icon='fa-box-open'
            )

        # Slow movers (low sales in 30 days)
        slow = (
            POSOrderItem.objects.filter(
                order__company=self.company, order__status='completed',
                order__created_at__gte=since_30
            )
            .values('product__name')
            .annotate(total_qty=Sum('quantity'))
            .filter(total_qty__lte=3)
            .order_by('total_qty')[:5]
        )
        if slow:
            slow_names = ', '.join(r['product__name'] for r in slow if r['product__name'])
            self._create_insight(
                title="Slow-moving products this month",
                description=f"These products had very low sales in the last 30 days (≤3 units): {slow_names}. Consider promotions.",
                category='inventory', priority='medium', color='yellow', icon='fa-snooze'
            )

        # Fast-movers generating 60%+ of revenue
        all_rev = POSOrderItem.objects.filter(
            order__company=self.company, order__status='completed',
            order__created_at__gte=since_30
        ).aggregate(t=Sum('total_price'))['t'] or Decimal('0')

        if all_rev > 0:
            top_products = (
                POSOrderItem.objects.filter(
                    order__company=self.company, order__status='completed',
                    order__created_at__gte=since_30
                )
                .values('product__name')
                .annotate(rev=Sum('total_price'))
                .order_by('-rev')
            )
            cumulative = Decimal('0')
            fast_movers = []
            for p in top_products:
                cumulative += p['rev'] or Decimal('0')
                fast_movers.append(p['product__name'])
                if cumulative / all_rev >= Decimal('0.6'):
                    break

            if len(fast_movers) <= 5:
                names = ', '.join(n for n in fast_movers if n)
                self._create_insight(
                    title=f"{len(fast_movers)} product(s) drive 60% of revenue",
                    description=f"These products generate over 60% of your revenue: {names}. Ensure optimal stock levels for these items.",
                    category='inventory', priority='medium', color='blue', icon='fa-fire'
                )

    # ─── STOCK INSIGHTS ───────────────────────────────────────────────────────

    def _stock_insights(self):
        from inventory.models import Product
        from analytics.models import StockForecast

        # Low stock + days-to-stockout
        critical_forecasts = StockForecast.objects.filter(
            company=self.company,
            stock_health__in=['low_risk', 'critical'],
            days_until_stockout__isnull=False,
        ).order_by('days_until_stockout')[:5]

        for fc in critical_forecasts:
            priority = 'high' if fc.stock_health == 'critical' else 'medium'
            color = 'red' if fc.stock_health == 'critical' else 'yellow'
            self._create_insight(
                title=f"Low stock: {fc.product.name}",
                description=f"{fc.product.name} may run out in {fc.days_until_stockout} day(s) based on recent sales. "
                            f"Recommended restock: {fc.recommended_restock_qty} units.",
                category='inventory', priority=priority, color=color, icon='fa-battery-low'
            )

        # Inventory value
        from django.db.models import ExpressionWrapper, DecimalField
        inv_value = (
            Product.objects.filter(company=self.company, status='active', stock_quantity__gt=0)
            .annotate(value=ExpressionWrapper(F('price') * F('stock_quantity'), output_field=DecimalField()))
            .aggregate(total=Sum('value'))['total'] or Decimal('0')
        )
        self._create_insight(
            title="Current inventory value",
            description=f"Your total stock on hand is worth approximately {self._fmt(inv_value)}.",
            category='financial', priority='low', color='blue', icon='fa-warehouse'
        )

        # Overstock
        overstock = StockForecast.objects.filter(
            company=self.company, stock_health='overstock'
        ).count()
        if overstock:
            self._create_insight(
                title=f"{overstock} overstocked product(s)",
                description=f"You have {overstock} product(s) with excess stock and low velocity. Consider reducing reorder quantities.",
                category='inventory', priority='low', color='yellow', icon='fa-boxes-stacked'
            )

    # ─── CUSTOMER INSIGHTS ────────────────────────────────────────────────────

    def _customer_insights(self):
        from sales.models import POSOrder, Customer

        since_30 = self.now - timedelta(days=30)
        since_60 = self.now - timedelta(days=60)

        # Top customer by spending this month
        top_cust = (
            POSOrder.objects.filter(
                company=self.company, status='completed',
                created_at__gte=since_30,
                customer__isnull=False,
            )
            .values('customer__name')
            .annotate(total=Sum('final_amount'))
            .order_by('-total')
            .first()
        )
        if top_cust and top_cust['customer__name']:
            self._create_insight(
                title=f"Top customer: {top_cust['customer__name']}",
                description=f"{top_cust['customer__name']} is your highest-spending customer this month with {self._fmt(top_cust['total'])}.",
                category='customers', priority='low', color='green', icon='fa-crown'
            )

        # Inactive customers (60 days no purchase)
        active_cust_ids = POSOrder.objects.filter(
            company=self.company, status='completed',
            created_at__gte=since_60, customer__isnull=False,
        ).values_list('customer_id', flat=True).distinct()

        inactive = Customer.objects.filter(
            company=self.company
        ).exclude(id__in=active_cust_ids).count()

        if inactive:
            self._create_insight(
                title=f"{inactive} inactive customer(s)",
                description=f"{inactive} registered customer(s) have not purchased in 60 days. Consider sending a re-engagement offer.",
                category='customers', priority='medium', color='yellow', icon='fa-user-clock'
            )

        # High credit risk (balance > 0 for 45+ days)
        risky = POSOrder.objects.filter(
            company=self.company,
            balance_amount__gt=0,
            created_at__lt=self.now - timedelta(days=45),
            customer__isnull=False,
        )
        if risky.exists():
            total_at_risk = risky.aggregate(t=Sum('balance_amount'))['t'] or Decimal('0')
            self._create_insight(
                title=f"High credit risk: {self._fmt(total_at_risk)} outstanding",
                description=f"{risky.count()} order(s) with a total of {self._fmt(total_at_risk)} unpaid for 45+ days. Follow up immediately.",
                category='customers', priority='high', color='red', icon='fa-triangle-exclamation'
            )

        # Credit summary
        total_credit = (
            POSOrder.objects.filter(company=self.company, balance_amount__gt=0)
            .aggregate(t=Sum('balance_amount'))['t'] or Decimal('0')
        )
        if total_credit > 0:
            self._create_insight(
                title=f"Total outstanding credit: {self._fmt(total_credit)}",
                description=f"Customers owe a combined total of {self._fmt(total_credit)}. Track and collect to improve cash flow.",
                category='financial', priority='medium', color='yellow', icon='fa-money-check-dollar'
            )

        # New customers this month
        new_custs = Customer.objects.filter(
            company=self.company,
            created_at__gte=since_30,
        ).count()
        if new_custs:
            self._create_insight(
                title=f"{new_custs} new customer(s) this month",
                description=f"You acquired {new_custs} new customer(s) in the last 30 days. Keep up the growth!",
                category='customers', priority='low', color='green', icon='fa-user-plus'
            )

    # ─── STAFF INSIGHTS ───────────────────────────────────────────────────────

    def _staff_insights(self):
        from sales.models import POSOrder

        since_7 = self.now - timedelta(days=7)
        today = self.today

        # Top performer this week
        top_staff = (
            POSOrder.objects.filter(
                company=self.company, status='completed',
                created_at__gte=since_7,
            )
            .values('cashier')
            .annotate(total=Sum('final_amount'), cnt=Count('id'))
            .order_by('-total')
            .first()
        )
        if top_staff and top_staff['cashier']:
            self._create_insight(
                title=f"Top performer: {top_staff['cashier']}",
                description=f"{top_staff['cashier']} generated {self._fmt(top_staff['total'])} across {top_staff['cnt']} order(s) this week — your highest performer.",
                category='staff', priority='low', color='green', icon='fa-medal'
            )

        # Low activity staff today
        all_staff = (
            POSOrder.objects.filter(company=self.company, status='completed', created_at__date=today)
            .values_list('cashier', flat=True).distinct()
        )
        staff_counts = (
            POSOrder.objects.filter(company=self.company, status='completed', created_at__date=today)
            .values('cashier')
            .annotate(cnt=Count('id'))
        )
        low_activity = [s for s in staff_counts if s['cnt'] <= 3]
        if low_activity:
            names = ', '.join(s['cashier'] for s in low_activity[:3])
            self._create_insight(
                title=f"Low activity staff today",
                description=f"Staff member(s) with very few orders today (≤3): {names}. Consider checking in.",
                category='staff', priority='low', color='yellow', icon='fa-user-tie'
            )

    # ─── FINANCIAL INSIGHTS ───────────────────────────────────────────────────

    def _financial_insights(self):
        from sales.models import POSOrder, POSOrderItem

        since_30 = self.now - timedelta(days=30)

        # Tax collected this month
        orders = POSOrder.objects.filter(
            company=self.company, status='completed', created_at__gte=since_30
        )
        tax_collected = orders.aggregate(t=Sum('tax_amount'))['t'] or Decimal('0')
        if tax_collected > 0:
            self._create_insight(
                title=f"Tax collected this month: {self._fmt(tax_collected)}",
                description=f"Total tax collected in the last 30 days is {self._fmt(tax_collected)}. Keep records for compliance.",
                category='financial', priority='low', color='blue', icon='fa-file-invoice-dollar'
            )

        # Revenue concentration (top 3 products = X% of revenue)
        all_rev = POSOrderItem.objects.filter(
            order__company=self.company, order__status='completed',
            order__created_at__gte=since_30
        ).aggregate(t=Sum('total_price'))['t'] or Decimal('0')

        top3_rev = (
            POSOrderItem.objects.filter(
                order__company=self.company, order__status='completed',
                order__created_at__gte=since_30
            )
            .values('product__name')
            .annotate(rev=Sum('total_price'))
            .order_by('-rev')[:3]
        )
        if all_rev > 0 and top3_rev:
            top3_total = sum(r['rev'] or Decimal('0') for r in top3_rev)
            pct = (top3_total / all_rev) * 100
            names = ', '.join(r['product__name'] for r in top3_rev if r['product__name'])
            self._create_insight(
                title=f"Top 3 products = {pct:.0f}% of revenue",
                description=f"{names} generate {pct:.0f}% of your monthly revenue ({self._fmt(top3_total)}). High concentration is a risk if any of these products go out of stock.",
                category='financial', priority='low' if pct < 70 else 'medium', color='blue', icon='fa-chart-pie'
            )

    # ─── MAIN RUNNER ─────────────────────────────────────────────────────────

    def generate_all(self):
        """Run all insight generators. Safe to call daily."""
        try:
            self._sales_insights()
        except Exception as e:
            logger.error(f"[Insights] Sales error for {self.company}: {e}")

        try:
            self._product_insights()
        except Exception as e:
            logger.error(f"[Insights] Product error for {self.company}: {e}")

        try:
            self._stock_insights()
        except Exception as e:
            logger.error(f"[Insights] Stock error for {self.company}: {e}")

        try:
            self._customer_insights()
        except Exception as e:
            logger.error(f"[Insights] Customer error for {self.company}: {e}")

        try:
            self._staff_insights()
        except Exception as e:
            logger.error(f"[Insights] Staff error for {self.company}: {e}")

        try:
            self._financial_insights()
        except Exception as e:
            logger.error(f"[Insights] Financial error for {self.company}: {e}")

        logger.info(f"[Insights] Generated all insights for {self.company.name}")
