"""
Per-platform connectors — Premium ETL เฟส 1 (Data Ingestion)
============================================================
หน้าที่เฟสนี้ (ขอบเขตชัด — ไม่ล้ำไปเฟสอื่น):
  ดึงข้อมูล "ออเดอร์ + สต็อก" จาก 5 แพลตฟอร์ม แล้ว normalize เป็น schema กลาง (models.py)
  *ยัง* ไม่เขียนลง Postgres (นั่นเฟส 2) และยังไม่ deploy (เฟส 3)

แนวทาง:
  - API-first: ดึงผ่าน API ทางการของแต่ละแพลตฟอร์ม (auth จาก env / Secret Manager)
  - Playwright fallback: ถ้า API ตาย/ไม่มี ใช้ headless browser ดึงจากหน้าเว็บแทน
  - retry: exponential backoff (tenacity) เฉพาะ error ชั่วคราว
  - SAMPLE_MODE=1: คืนข้อมูลตัวอย่างในตัว เพื่อรัน/เทส pipeline ได้โดยไม่ต้องมี credential จริง

heavy deps (requests/tenacity/playwright) ถูก import แบบ lazy ในเมธอด — โมดูลนี้ import ได้ด้วย stdlib ล้วน
"""
from __future__ import annotations

import os
import logging
from abc import ABC, abstractmethod

from models import OrderRecord, InventoryRecord, ExtractResult

log = logging.getLogger("connectors")
SAMPLE_MODE = os.environ.get("SAMPLE_MODE", "0") == "1"


# ---------------------------------------------------------------- base
class Connector(ABC):
    platform: str = "base"

    def __init__(self, sample: bool | None = None):
        self.sample = SAMPLE_MODE if sample is None else sample

    # ---- network helpers (lazy imports keep this file importable w/o deps) ----
    def _get_json(self, url: str, *, headers: dict | None = None, params: dict | None = None) -> dict:
        import requests
        from tenacity import (Retrying, stop_after_attempt,
                              wait_exponential, retry_if_exception_type)
        for attempt in Retrying(
            stop=stop_after_attempt(4),
            wait=wait_exponential(multiplier=1, min=2, max=30),
            retry=retry_if_exception_type(requests.RequestException),
            reraise=True,
        ):
            with attempt:
                r = requests.get(url, headers=headers or {}, params=params or {}, timeout=30)
                r.raise_for_status()
                return r.json()

    def _playwright_html(self, url: str) -> str:
        from playwright.sync_api import sync_playwright
        with sync_playwright() as p:
            b = p.chromium.launch(headless=True)
            page = b.new_page(user_agent=("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                                          "AppleWebKit/537.36 Chrome/124.0 Safari/537.36"))
            page.goto(url, wait_until="networkidle", timeout=45000)
            html = page.content()
            b.close()
            return html

    # ---- per-platform implementation ----
    @abstractmethod
    def _fetch_raw(self) -> dict:
        """ดึง payload ดิบจากแพลตฟอร์ม (API หรือ Playwright). SAMPLE_MODE -> ตัวอย่างในตัว."""

    @abstractmethod
    def _normalize(self, raw: dict) -> tuple[list, list]:
        """แปลง payload ดิบ -> (orders: list[OrderRecord], inventory: list[InventoryRecord])."""

    def extract(self) -> ExtractResult:
        """เมธอดหลัก: ดึง -> normalize -> ห่อผลลัพธ์. ความล้มเหลวถูกจับ ไม่ลามไป connector อื่น."""
        try:
            raw = self._fetch_raw()
            orders, inventory = self._normalize(raw)
            src = "sample" if self.sample else raw.get("_source", "api")
            return ExtractResult(self.platform, orders, inventory, ok=True, source=src)
        except Exception as e:                      # noqa: BLE001 — เก็บ error ไว้ ไม่ throw
            log.exception("extract failed: %s", self.platform)
            return ExtractResult(self.platform, ok=False, error=f"{type(e).__name__}: {e}")


# ---------------------------------------------------------------- Shopee
class ShopeeConnector(Connector):
    platform = "shopee"

    def _fetch_raw(self) -> dict:
        if self.sample:
            return {"_source": "sample", "orders": [
                {"order_sn": "SP-100A", "create_time": "2026-06-06T07:10:00Z", "order_status": "READY_TO_SHIP",
                 "items": [{"item_sku": "SKU-LEGGING-01", "model_quantity_purchased": 2, "model_discounted_price": 690}]},
            ], "stock": [{"item_sku": "SKU-LEGGING-01", "normal_stock": 48}]}
        token = os.environ["SHOPEE_ACCESS_TOKEN"]   # from Secret Manager
        # Shopee Open Platform v2: signed requests (partner_id + shop_id + HMAC-SHA256)
        data = self._get_json("https://partner.shopeemobile.com/api/v2/order/get_order_list",
                              headers={"Authorization": token})
        data["_source"] = "api"
        return data

    def _normalize(self, raw: dict) -> tuple[list, list]:
        orders, inv = [], []
        for o in raw.get("orders", []):
            for it in o.get("items", []):
                orders.append(OrderRecord(
                    platform=self.platform, platform_order_id=o["order_sn"],
                    order_date=o["create_time"], total_amount=float(it["model_discounted_price"]) * it["model_quantity_purchased"],
                    status=o["order_status"], sku=it["item_sku"],
                    qty=int(it["model_quantity_purchased"]), unit_price=float(it["model_discounted_price"]),
                ))
        for s in raw.get("stock", []):
            inv.append(InventoryRecord(self.platform, s["item_sku"], int(s["normal_stock"])))
        return orders, inv


# ---------------------------------------------------------------- Lazada
class LazadaConnector(Connector):
    platform = "lazada"

    def _fetch_raw(self) -> dict:
        if self.sample:
            return {"_source": "sample",
                    "orders": [{"order_number": "LZ-22B", "created_at": "2026-06-06T06:40:00Z", "statuses": ["packed"],
                                "sku": "SKU-SHOE-RUN-22", "quantity": 1, "item_price": 1890}],
                    "stock": [{"sku": "SKU-SHOE-RUN-22", "quantity": 12}]}
        os.environ["LAZADA_APP_KEY"]                 # app_key + sign per Lazada Open Platform
        data = self._get_json("https://api.lazada.co.th/rest/orders/get")
        data["_source"] = "api"
        return data

    def _normalize(self, raw: dict) -> tuple[list, list]:
        orders = [OrderRecord(self.platform, o["order_number"], o["created_at"],
                              float(o["item_price"]) * o["quantity"], o["statuses"][0],
                              o["sku"], int(o["quantity"]), float(o["item_price"]))
                  for o in raw.get("orders", [])]
        inv = [InventoryRecord(self.platform, s["sku"], int(s["quantity"])) for s in raw.get("stock", [])]
        return orders, inv


# ---------------------------------------------------------------- TikTok Shop
class TikTokConnector(Connector):
    platform = "tiktok"

    def _fetch_raw(self) -> dict:
        if self.sample:
            return {"_source": "sample",
                    "order_list": [{"order_id": "TT-77C", "create_time": 1749196800, "order_status": "AWAITING_SHIPMENT",
                                    "sku_id": "SKU-BOTTLE-08", "quantity": 3, "sale_price": 290}],
                    "inventory": [{"sku_id": "SKU-BOTTLE-08", "available": 5}]}
        # TikTok Shop Partner API: shop_cipher + access_token; API ชอบล่มช่วงพีค -> fallback
        os.environ["TIKTOK_ACCESS_TOKEN"]
        data = self._get_json("https://open-api.tiktokglobalshop.com/order/202309/orders/search")
        data["_source"] = "api"
        return data

    def _normalize(self, raw: dict) -> tuple[list, list]:
        from datetime import datetime, timezone
        orders = []
        for o in raw.get("order_list", []):
            dt = datetime.fromtimestamp(o["create_time"], tz=timezone.utc).isoformat(timespec="seconds")
            orders.append(OrderRecord(self.platform, o["order_id"], dt,
                                      float(o["sale_price"]) * o["quantity"], o["order_status"],
                                      o["sku_id"], int(o["quantity"]), float(o["sale_price"])))
        inv = [InventoryRecord(self.platform, s["sku_id"], int(s["available"])) for s in raw.get("inventory", [])]
        return orders, inv


# ---------------------------------------------------------------- Shopify
class ShopifyConnector(Connector):
    platform = "shopify"

    def _fetch_raw(self) -> dict:
        if self.sample:
            return {"_source": "sample",
                    "orders": [{"id": "SH-9001", "created_at": "2026-06-06T05:15:00Z", "financial_status": "paid",
                                "line_items": [{"sku": "SKU-LEGGING-01", "quantity": 1, "price": 720}]}],
                    "inventory": [{"sku": "SKU-LEGGING-01", "available": 30}]}
        token = os.environ["SHOPIFY_ACCESS_TOKEN"]
        data = self._get_json("https://urbanactive.myshopify.com/admin/api/2024-04/orders.json",
                              headers={"X-Shopify-Access-Token": token})
        data["_source"] = "api"
        return data

    def _normalize(self, raw: dict) -> tuple[list, list]:
        orders = []
        for o in raw.get("orders", []):
            for li in o.get("line_items", []):
                orders.append(OrderRecord(self.platform, str(o["id"]), o["created_at"],
                                          float(li["price"]) * li["quantity"], o["financial_status"],
                                          li["sku"], int(li["quantity"]), float(li["price"])))
        inv = [InventoryRecord(self.platform, s["sku"], int(s["available"])) for s in raw.get("inventory", [])]
        return orders, inv


# ---------------------------------------------------------------- LINE Shopping
class LineShopConnector(Connector):
    platform = "line"

    def _fetch_raw(self) -> dict:
        if self.sample:
            return {"_source": "sample",
                    "orders": [{"id": "LN-55D", "ts": "2026-06-06T04:50:00Z", "state": "confirmed",
                                "sku": "SKU-SHOE-RUN-22", "qty": 1, "price": 1850}],
                    "stock": [{"sku": "SKU-SHOE-RUN-22", "qty": 9}]}
        # LINE Shopping ไม่มี public order API เต็มรูป -> ใช้ Playwright fallback ดึงจาก merchant console
        html = self._playwright_html("https://shopping.line.me/merchant/orders")
        return {"_source": "playwright", "raw_html": html, "orders": [], "stock": []}

    def _normalize(self, raw: dict) -> tuple[list, list]:
        orders = [OrderRecord(self.platform, o["id"], o["ts"], float(o["price"]) * o["qty"],
                              o["state"], o["sku"], int(o["qty"]), float(o["price"]))
                  for o in raw.get("orders", [])]
        inv = [InventoryRecord(self.platform, s["sku"], int(s["qty"])) for s in raw.get("stock", [])]
        return orders, inv


CONNECTORS = [ShopeeConnector, LazadaConnector, TikTokConnector, ShopifyConnector, LineShopConnector]
