"""
TikTok Shop API Client - via Apify (pratikdani actors)

Replaces EchoTik API ($139/mo) with Apify actors (~$5-15/mo):
- pratikdani/tiktok-shop-search-scraper: Search products with GMV/sales ($0.15/10 products)
- pratikdani/tiktok-shop-scraper: Product detail with full data ($0.002/product)

All data normalized to match existing EchoTik format for seamless frontend compatibility.
"""

import json
import logging
import re
import time
from datetime import datetime

import requests

logger = logging.getLogger(__name__)

APIFY_BASE = "https://api.apify.com/v2"
TIMEOUT = 30
WAIT_TIMEOUT = 180  # max seconds to wait for actor run


class TikTokShopClient:
    """Apify-based TikTok Shop data client (Brazil region)."""

    def __init__(self, api_token: str):
        self.token = api_token
        self.headers = {
            "Authorization": f"Bearer {api_token}",
            "Content-Type": "application/json",
        }

    def _run_actor(self, actor_id: str, input_data: dict, timeout: int = WAIT_TIMEOUT) -> list:
        """Run an Apify actor and return dataset items."""
        url = f"{APIFY_BASE}/acts/{actor_id}/runs?waitForFinish={timeout}"
        try:
            r = requests.post(url, headers=self.headers, json=input_data, timeout=timeout + 30)
            r.raise_for_status()
            data = r.json()

            if "error" in data:
                msg = data["error"].get("message", "Unknown error")
                logger.warning(f"Apify actor error ({actor_id}): {msg}")
                raise ValueError(f"Apify: {msg}")

            run_data = data.get("data", {})
            status = run_data.get("status")
            dataset_id = run_data.get("defaultDatasetId")

            if status != "SUCCEEDED" or not dataset_id:
                logger.warning(f"Apify run {actor_id} status={status}")
                raise ValueError(f"Apify run failed: status={status}")

            # Fetch dataset items
            items_url = f"{APIFY_BASE}/datasets/{dataset_id}/items?format=json"
            r2 = requests.get(items_url, headers=self.headers, timeout=TIMEOUT)
            r2.raise_for_status()
            return r2.json()

        except requests.exceptions.Timeout:
            logger.error(f"Apify actor timeout ({actor_id})")
            raise ValueError("Apify: timeout na execucao")
        except ValueError:
            raise
        except Exception as e:
            logger.error(f"Apify actor error ({actor_id}): {e}")
            raise

    # ─── Product Search ───────────────────────────────────────

    def product_search(self, keyword: str, max_results: int = 10) -> list:
        """Search TikTok Shop products by keyword (Brazil).

        Uses pratikdani/tiktok-shop-search-scraper.
        Cost: ~$0.15 per 10 products.
        """
        items = self._run_actor("pratikdani~tiktok-shop-search-scraper", {
            "keyword": keyword,
            "country_code": "BR",
            "maxResults": max_results,
        })
        return [_normalize_search_product(p) for p in items]

    def product_ranking(self, sort_by: str = "sales", max_results: int = 10,
                        category: str = "") -> list:
        """Get top products ranked by sales/GMV.

        Simulates ranking via search with popular keywords sorted by metrics.
        """
        # Use broad keywords to get popular products
        keywords = ["tendencia", "mais vendido", "popular"]
        if category:
            keywords = [category]

        all_products = []
        for kw in keywords[:1]:  # Save credits - 1 search
            try:
                products = self.product_search(kw, max_results)
                all_products.extend(products)
            except Exception as e:
                logger.warning(f"Ranking search failed for '{kw}': {e}")

        # Sort by requested metric
        if sort_by == "gmv":
            all_products.sort(key=lambda p: p.get("gmv_total", 0), reverse=True)
        else:  # sales
            all_products.sort(key=lambda p: p.get("sales_total", 0), reverse=True)

        # Deduplicate
        seen = set()
        unique = []
        for p in all_products:
            pid = p.get("product_id")
            if pid and pid not in seen:
                seen.add(pid)
                unique.append(p)

        return unique[:max_results]

    # ─── Product Detail ───────────────────────────────────────

    def product_detail(self, product_id: str) -> dict:
        """Get full product details including GMV, stock, sales, images.

        Uses pratikdani/tiktok-shop-scraper.
        Cost: ~$0.002 per product.
        """
        url = f"https://www.tiktok.com/view/product/{product_id}"
        items = self._run_actor("pratikdani~tiktok-shop-scraper", {
            "url": url,
        })
        if not items:
            raise ValueError("Produto nao encontrado")
        return _normalize_detail_product(items[0])

    # ─── Seller Operations ────────────────────────────────────

    def seller_ranking(self, sort_by: str = "sales") -> list:
        """Build seller ranking from product search data.

        Aggregates seller data from product searches.
        """
        # Search broad keywords and aggregate sellers
        products = self.product_search("mais vendido", 10)
        sellers = {}
        for p in products:
            sid = p.get("seller_id")
            if not sid:
                continue
            if sid not in sellers:
                sellers[sid] = {
                    "seller_id": sid,
                    "name": p.get("seller_name", ""),
                    "logo": p.get("seller_logo", ""),
                    "rating": 0,
                    "category": "",
                    "product_count": 0,
                    "sales_total": 0,
                    "sales_formatted": "0",
                    "gmv_total": 0,
                    "gmv_formatted": "R$ 0",
                    "influencer_count": 0,
                    "video_count": 0,
                    "region": "BR",
                }
            s = sellers[sid]
            s["product_count"] += 1
            s["sales_total"] += p.get("seller_sales", 0)
            s["gmv_total"] += p.get("seller_gmv", 0)

        result = list(sellers.values())
        for s in result:
            s["sales_formatted"] = _format_number(s["sales_total"])
            s["gmv_formatted"] = _format_brl(s["gmv_total"])

        if sort_by == "gmv":
            result.sort(key=lambda x: x["gmv_total"], reverse=True)
        else:
            result.sort(key=lambda x: x["sales_total"], reverse=True)

        return result

    def seller_detail(self, seller_id: str) -> dict:
        """Get seller details (extracted from cached product data)."""
        # We don't have a direct seller endpoint, return from cache
        raise ValueError("Detalhe de loja sera carregado do cache local")

    def seller_products(self, seller_id: str, page: int = 1) -> list:
        """Get products from a specific seller."""
        # Search by seller name would require knowing it
        raise ValueError("Produtos da loja serao carregados do cache local")


# ─── Normalizers ─────────────────────────────────────────────


def _parse_number(val) -> float:
    """Parse formatted numbers like '8.16K', 'R$2.45M', '$465.83K'."""
    if val is None:
        return 0.0
    if isinstance(val, (int, float)):
        return float(val)
    s = str(val).strip()
    if not s:
        return 0.0

    # Remove currency prefixes
    s = re.sub(r'^[R$\s]+', '', s)
    s = s.replace(',', '')

    multiplier = 1.0
    if s.upper().endswith('K'):
        multiplier = 1_000
        s = s[:-1]
    elif s.upper().endswith('M'):
        multiplier = 1_000_000
        s = s[:-1]
    elif s.upper().endswith('B'):
        multiplier = 1_000_000_000
        s = s[:-1]

    try:
        return float(s) * multiplier
    except (ValueError, TypeError):
        return 0.0


def _parse_price_brl(val) -> float:
    """Parse BRL price like 'R$ 68,00' or 'R$300.71'."""
    if not val:
        return 0.0
    s = str(val).strip()
    s = re.sub(r'^R\$\s*', '', s)
    # Handle Brazilian format: R$ 1.234,56
    if ',' in s and '.' in s:
        s = s.replace('.', '').replace(',', '.')
    elif ',' in s:
        s = s.replace(',', '.')
    try:
        return float(s)
    except (ValueError, TypeError):
        return 0.0


def _format_brl(value) -> str:
    """Format number as BRL currency."""
    try:
        v = float(value or 0)
        if v >= 1_000_000:
            return f"R$ {v/1_000_000:.1f}M"
        if v >= 1_000:
            return f"R$ {v/1_000:.1f}K"
        return f"R$ {v:.2f}"
    except (ValueError, TypeError):
        return "R$ 0"


def _format_number(n) -> str:
    try:
        n = float(n or 0)
        if n >= 1_000_000:
            return f"{n/1_000_000:.1f}M"
        if n >= 1_000:
            return f"{n/1_000:.1f}K"
        return str(int(n))
    except (ValueError, TypeError):
        return "0"


def _normalize_search_product(p: dict) -> dict:
    """Normalize product from pratikdani search scraper to match EchoTik format."""
    pid = str(p.get("product_id", ""))
    sale_cnt = _parse_number(p.get("total_sale_cnt") or p.get("total_sale_30d_cnt", 0))
    gmv = _parse_number(p.get("total_sale_gmv_amt") or p.get("total_sale_gmv_30d_amt", 0))
    price = _parse_price_brl(p.get("real_price") or p.get("min_price") or p.get("min_price_fz") or p.get("original_price", "0"))
    commission_str = str(p.get("commission", "") or "")
    commission = float(commission_str.replace('%', '')) / 100 if commission_str.replace('%', '').replace('.', '').isdigit() else 0

    # Extract seller info
    seller = p.get("seller", {}) or {}
    seller_id = str(seller.get("seller_id", ""))
    seller_name = seller.get("seller_name", "")
    seller_sales = _parse_number(seller.get("total_sale_cnt", 0))
    seller_gmv = _parse_number(seller.get("total_sale_gmv_amt", 0))
    seller_cover = seller.get("cover_url", "")

    # Image URL - use cover_url from API (CDN works for pratikdani)
    cover_url = p.get("cover_url", "")
    # Also extract SKU images from sale_props
    sku_image = ""
    sale_props = p.get("sale_props", [])
    if isinstance(sale_props, list):
        for prop in sale_props:
            if prop.get("has_image"):
                for val in prop.get("sale_prop_values", []):
                    img = val.get("image", "")
                    if img and ("ibyteimg.com" in img or "tiktokcdn" in img):
                        sku_image = img
                        break
                if sku_image:
                    break

    image = cover_url or sku_image

    return {
        "product_id": pid,
        "title": p.get("product_name") or p.get("product_title", ""),
        "image": image,
        "price": price,
        "price_formatted": _format_brl(price),
        "original_price": _parse_price_brl(p.get("original_price", "0")),
        "min_price": _parse_price_brl(p.get("min_price") or p.get("min_price_fz", "0")),
        "max_price": _parse_price_brl(p.get("max_price") or p.get("max_price_fz", "0")),
        "discount": "",
        "sales_total": int(sale_cnt),
        "sales_formatted": _format_number(sale_cnt),
        "sales_7d": int(_parse_number(p.get("total_sale_7d_cnt", 0))),
        "sales_30d": int(_parse_number(p.get("total_sale_30d_cnt", 0))),
        "gmv_total": gmv,
        "gmv_formatted": _format_brl(gmv),
        "gmv_7d": _parse_number(p.get("total_sale_gmv_7d_amt", 0)),
        "gmv_30d": _parse_number(p.get("total_sale_gmv_30d_amt", 0)),
        "commission_rate": commission,
        "commission_pct": f"{commission*100:.0f}%" if commission else "",
        "rating": float(p.get("product_rating", 0) or 0),
        "review_count": int(_parse_number(p.get("review_count", 0))),
        "influencer_count": int(_parse_number(p.get("influencers_count") or p.get("total_ifl_cnt", 0))),
        "video_count": int(_parse_number(p.get("videos_count") or p.get("total_video_count", 0))),
        "live_count": int(_parse_number(p.get("lives_count") or p.get("total_live_count", 0))),
        "view_count": int(_parse_number(p.get("view_count", 0))),
        "seller_id": seller_id,
        "seller_name": seller_name,
        "seller_sales": int(seller_sales),
        "seller_gmv": seller_gmv,
        "seller_logo": seller_cover,
        "category": p.get("category", ""),
        "categories": p.get("categories", ""),
        "category_id": "",
        "region": "BR",
        "is_hot": bool(p.get("is_hot", 0)),
        "source": "apify_search",
    }


def _normalize_detail_product(p: dict) -> dict:
    """Normalize product from pratikdani detail scraper."""
    pid = str(p.get("product_id", ""))
    sale_cnt = _parse_number(p.get("sale_cnt", 0))
    sale_30d = _parse_number(p.get("sale_30d_cnt", 0))
    gmv = _parse_number(p.get("gmv_amt") or p.get("gmv_30d_amt", 0))
    gmv_30d = _parse_number(p.get("gmv_30d_amt", 0))
    price = _parse_price_brl(p.get("real_price") or p.get("original_price", "0"))
    total_stock = _parse_number(p.get("total_stock_cnt", 0))

    commission_str = str(p.get("commission", "") or "")
    commission = float(commission_str.replace('%', '')) / 100 if commission_str.replace('%', '').replace('.', '').isdigit() else 0

    # Seller info
    seller = p.get("seller", {}) or {}
    seller_id = str(seller.get("seller_id", ""))

    # Images
    images = p.get("images", []) or []
    cover_url = images[0] if images else p.get("cover_url", "")

    # SKUs
    skus_raw = p.get("skus", {})
    skus = []
    if isinstance(skus_raw, dict):
        for sku_key, sku_data in skus_raw.items():
            price_info = sku_data.get("real_price", {}) or {}
            skus.append({
                "sku_id": sku_key,
                "stock": int(_parse_number(sku_data.get("stock_cnt", 0))),
                "price": price_info.get("price_str", ""),
            })

    # Labels
    labels = p.get("labels", {}) or {}

    # Specifications
    specs = p.get("specification", []) or []

    # Product description (list of text/image items)
    desc_items = p.get("product_desc", []) or []
    description = ""
    for item in desc_items[:5]:
        if isinstance(item, dict) and item.get("type") == "text":
            description += item.get("content", "") + " "
        elif isinstance(item, str):
            description += item + " "

    return {
        "product_id": pid,
        "title": p.get("product_name", ""),
        "image": cover_url,
        "images": images,
        "description": description.strip(),
        "price": price,
        "price_formatted": _format_brl(price),
        "original_price": _parse_price_brl(p.get("original_price", "0")),
        "min_price": _parse_number(p.get("min_price", 0)),
        "max_price": _parse_number(p.get("max_price", 0)),
        "discount": "",
        "sales_total": int(sale_cnt),
        "sales_formatted": _format_number(sale_cnt),
        "sales_7d": 0,
        "sales_15d": 0,
        "sales_30d": int(sale_30d),
        "sales_90d": 0,
        "gmv_total": gmv,
        "gmv_formatted": _format_brl(gmv),
        "gmv_7d": 0,
        "gmv_15d": 0,
        "gmv_30d": gmv_30d,
        "gmv_30d_formatted": _format_brl(gmv_30d),
        "gmv_90d": 0,
        "commission_rate": commission,
        "commission_pct": f"{commission*100:.0f}%" if commission else "",
        "rating": float(labels.get("rating", 0) or p.get("product_rating", 0) or 0),
        "review_count": int(_parse_number(p.get("review_count", 0))),
        "influencer_count": int(_parse_number(p.get("total_ifl_cnt", 0))),
        "video_count": int(_parse_number(p.get("total_video_cnt", 0))),
        "live_count": int(_parse_number(p.get("total_live_cnt", 0))),
        "videos_30d": int(_parse_number(p.get("video_30d_cnt", 0))),
        "influencers_30d": int(_parse_number(p.get("ifl_30d_cnt", 0))),
        "views_total": 0,
        "stock": int(total_stock),
        "skus": skus,
        "seller_id": seller_id,
        "seller_name": seller.get("seller_name", ""),
        "seller_sales": int(_parse_number(seller.get("total_sale_cnt", 0))),
        "seller_gmv": _parse_number(seller.get("total_sale_gmv_amt", 0)),
        "seller_logo": seller.get("cover_url", ""),
        "category": p.get("category", ""),
        "categories": p.get("categories", ""),
        "category_id": str(p.get("category_ids", {}).get("category_id", "")),
        "specifications": specs,
        "product_url": p.get("product_url", f"https://www.tiktok.com/shop/br/pdp/{pid}"),
        "update_time": p.get("update_time", ""),
        "first_time": p.get("first_time", ""),
        "free_shipping": bool(labels.get("is_free_shipping", 0)),
        "category_rank": labels.get("total_category_sale_rank", ""),
        "region": "BR",
        "source": "apify_detail",
    }
