"""
EchoTik Cache Service - Cache-first access to EchoTik TikTok Shop data.

Stores normalized API responses as JSON in SQLite.
Serves from cache when fresh, fetches from API only on miss/expiry.
Falls back to stale cache if API fails.
"""

import json
import logging
from datetime import datetime, timedelta

from backend.database.connection import SessionLocal
from backend.models.echotik_cache import EchoTikCache
from backend.services.echotik import EchoTikClient

logger = logging.getLogger(__name__)

# TTL in hours per cache type
TTL_HOURS = {
    "product_ranking": 6,
    "seller_ranking": 6,
    "influencer_ranking": 6,
    "video_ranking": 6,
    "product_search": 12,
    "seller_search": 12,
    "product_detail": 24,
    "product_trend": 6,
    "product_videos": 12,
    "product_influencers": 12,
    "seller_detail": 24,
    "seller_products": 12,
}


class EchoTikCacheService:
    def __init__(self, username: str, api_key: str):
        self.client = EchoTikClient(username, api_key)

    def _get_or_fetch(self, cache_type: str, cache_key: str, fetch_fn, ttl_hours: int = None):
        """Core cache pattern: serve from DB if fresh, otherwise fetch and store."""
        if ttl_hours is None:
            ttl_hours = TTL_HOURS.get(cache_type, 6)

        db = SessionLocal()
        try:
            row = db.query(EchoTikCache).filter(EchoTikCache.cache_key == cache_key).first()

            # Cache hit and still fresh
            if row and row.expires_at and datetime.utcnow() < row.expires_at:
                return json.loads(row.data_json)

            # Cache miss or expired - try to fetch
            try:
                data = fetch_fn()
                # Only cache non-empty results (avoid caching API errors/rate limits)
                is_empty = (isinstance(data, list) and len(data) == 0)
                if not is_empty:
                    self._save_cache(db, cache_type, cache_key, data, ttl_hours)
                elif row:
                    # API returned empty but we have stale data - keep stale
                    logger.info(f"API returned empty for {cache_key}, serving stale cache")
                    return json.loads(row.data_json)
                return data
            except Exception as e:
                logger.warning(f"EchoTik fetch failed ({cache_type}/{cache_key}): {e}")
                # Return stale cache if available
                if row:
                    logger.info(f"Serving stale cache for {cache_key}")
                    return json.loads(row.data_json)
                raise
        finally:
            db.close()

    def _save_cache(self, db, cache_type: str, cache_key: str, data, ttl_hours: int):
        """Save or update cache entry."""
        now = datetime.utcnow()
        expires = now + timedelta(hours=ttl_hours)
        data_str = json.dumps(data, ensure_ascii=False)
        count = len(data) if isinstance(data, list) else 1

        row = db.query(EchoTikCache).filter(EchoTikCache.cache_key == cache_key).first()
        if row:
            row.data_json = data_str
            row.item_count = count
            row.fetched_at = now
            row.expires_at = expires
        else:
            db.add(EchoTikCache(
                cache_type=cache_type,
                cache_key=cache_key,
                data_json=data_str,
                item_count=count,
                fetched_at=now,
                expires_at=expires,
            ))
        db.commit()

    # ─── Product Endpoints ───────────────────────────────

    def product_ranking(self, date="", rank_field=1, page=1, page_size=10, category_id=""):
        key = f"product_ranking:rf{rank_field}:p{page}:ps{page_size}:cat_{category_id}"
        return self._get_or_fetch("product_ranking", key,
            lambda: self.client.product_ranking(date, rank_field, page=page,
                                                 page_size=page_size, category_id=category_id))

    def product_search(self, keyword="", page=1, page_size=10):
        key = f"product_search:kw_{keyword}:p{page}:ps{page_size}"
        return self._get_or_fetch("product_search", key,
            lambda: self.client.product_search(keyword, page, page_size))

    def product_detail(self, product_id: str):
        key = f"product_detail:{product_id}"
        return self._get_or_fetch("product_detail", key,
            lambda: self.client.product_detail(product_id))

    def product_trend(self, product_id: str, days: int = 7):
        key = f"product_trend:{product_id}:d{days}"
        return self._get_or_fetch("product_trend", key,
            lambda: self.client.product_trend(product_id, days))

    def product_videos(self, product_id: str, page=1, page_size=10):
        key = f"product_videos:{product_id}:p{page}"
        return self._get_or_fetch("product_videos", key,
            lambda: self.client.product_videos(product_id, page, page_size))

    def product_influencers(self, product_id: str, page=1, page_size=10):
        key = f"product_influencers:{product_id}:p{page}"
        return self._get_or_fetch("product_influencers", key,
            lambda: self.client.product_influencers(product_id, page, page_size))

    # ─── Seller Endpoints ────────────────────────────────

    def seller_ranking(self, date="", rank_field=1, page=1, page_size=10):
        key = f"seller_ranking:rf{rank_field}:p{page}:ps{page_size}"
        return self._get_or_fetch("seller_ranking", key,
            lambda: self.client.seller_ranking(date, rank_field, page=page, page_size=page_size))

    def seller_detail(self, seller_id: str):
        key = f"seller_detail:{seller_id}"
        return self._get_or_fetch("seller_detail", key,
            lambda: self.client.seller_detail(seller_id))

    def seller_products(self, seller_id: str, page=1, page_size=10):
        key = f"seller_products:{seller_id}:p{page}"
        return self._get_or_fetch("seller_products", key,
            lambda: self.client.seller_products(seller_id, page, page_size))

    def seller_search(self, keyword="", page=1, page_size=10):
        key = f"seller_search:kw_{keyword}:p{page}:ps{page_size}"
        return self._get_or_fetch("seller_search", key,
            lambda: self.client.seller_search(keyword, page, page_size))

    # ─── Influencer Endpoints ────────────────────────────

    def influencer_ranking(self, date="", rank_field=1, page=1, page_size=10):
        key = f"influencer_ranking:rf{rank_field}:p{page}:ps{page_size}"
        return self._get_or_fetch("influencer_ranking", key,
            lambda: self.client.influencer_ranking(date, rank_field, page=page, page_size=page_size))

    # ─── Video Endpoints ─────────────────────────────────

    def video_ranking(self, date="", rank_field=1, page=1, page_size=10):
        key = f"video_ranking:rf{rank_field}:p{page}:ps{page_size}"
        return self._get_or_fetch("video_ranking", key,
            lambda: self.client.video_ranking(date, rank_field, page=page, page_size=page_size))

    # ─── Sync & Maintenance ──────────────────────────────

    def sync_rankings(self) -> int:
        """Pre-fetch page 1 of all rankings (called by scheduler)."""
        synced = 0
        tasks = [
            ("product_ranking", [1, 2]),    # rank_field 1=sales, 2=gmv
            ("seller_ranking", [1, 2]),
            ("influencer_ranking", [1]),
            ("video_ranking", [1, 2]),
        ]
        for cache_type, rank_fields in tasks:
            for rf in rank_fields:
                try:
                    method = getattr(self, cache_type)
                    method(rank_field=rf, page=1, page_size=10)
                    synced += 1
                except Exception as e:
                    logger.warning(f"Sync failed for {cache_type} rf={rf}: {e}")
        return synced

    @staticmethod
    def clear_expired():
        """Delete expired cache entries."""
        db = SessionLocal()
        try:
            deleted = db.query(EchoTikCache).filter(
                EchoTikCache.expires_at < datetime.utcnow() - timedelta(hours=24)
            ).delete()
            db.commit()
            if deleted:
                logger.info(f"Cleared {deleted} expired EchoTik cache entries")
        finally:
            db.close()

    @staticmethod
    def get_cache_stats() -> dict:
        """Get cache statistics."""
        db = SessionLocal()
        try:
            total = db.query(EchoTikCache).count()
            fresh = db.query(EchoTikCache).filter(
                EchoTikCache.expires_at > datetime.utcnow()
            ).count()
            return {"total_entries": total, "fresh": fresh, "expired": total - fresh}
        finally:
            db.close()
