import logging
from typing import Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.crypto import decrypt_secret
from app.models.cloud_server import CloudServer, CloudServerType
from app.models.product import Product, ProductApiType, StockPolicy
from app.services.zjmf_finance_client import ZjmfFinanceClient, ZjmfFinanceCredentials, ZjmfFinanceError

log = logging.getLogger("qycw.zjmf_stock_sync")


def _enum_value(value: Any) -> str:
    return getattr(value, "value", value)


def _truthy(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        return value != 0
    if isinstance(value, str):
        return value.strip().lower() in {"1", "true", "yes", "on"}
    return False


def _int_or_none(value: Any) -> int | None:
    if value in (None, ""):
        return None
    try:
        return int(float(str(value).strip()))
    except (TypeError, ValueError):
        return None


def _product_from_config(raw_config: dict[str, Any]) -> dict[str, Any]:
    if isinstance(raw_config.get("products"), dict):
        return raw_config["products"]
    if isinstance(raw_config.get("product"), dict):
        return raw_config["product"]
    return {}


def _client_for_server(server: CloudServer) -> ZjmfFinanceClient:
    creds = ZjmfFinanceCredentials(
        base_url=server.base_url,
        username=server.username,
        password=decrypt_secret(server.password_encrypted),
        account_type=server.account_type,
    )
    return ZjmfFinanceClient(creds, server_id=server.id, timeout=20.0)


def _desired_stock(raw_config: dict[str, Any]) -> dict[str, Any]:
    product = _product_from_config(raw_config)
    stock_control = _truthy(product.get("stock_control"))
    qty = _int_or_none(product.get("qty"))
    stock_policy = StockPolicy.TRACKED.value if stock_control else StockPolicy.UNLIMITED.value
    stock = max(0, qty or 0) if stock_control else 0
    return {
        "upstream_pid": str(product.get("id") or ""),
        "upstream_name": product.get("name"),
        "stock_control": product.get("stock_control"),
        "stock_control_enabled": stock_control,
        "qty": qty,
        "stock_policy": stock_policy,
        "stock": stock,
        "hidden": product.get("hidden"),
        "hidden_enabled": _truthy(product.get("hidden")),
        "retired": product.get("retired"),
        "retired_enabled": _truthy(product.get("retired")),
    }


async def sync_zjmf_product_stocks(db: AsyncSession, *, limit: int = 500) -> dict[str, Any]:
    rows = (
        await db.execute(
            select(Product, CloudServer)
            .join(CloudServer, CloudServer.id == Product.cloud_server_id)
            .where(
                Product.api_type == ProductApiType.ZJMF_FINANCE.value,
                Product.cloud_server_id.is_not(None),
                Product.api_identifier.is_not(None),
                CloudServer.server_type == CloudServerType.ZJMF_FINANCE.value,
                CloudServer.disabled.is_(False),
            )
            .order_by(CloudServer.id.asc(), Product.id.asc())
            .limit(limit)
        )
    ).all()

    summary: dict[str, Any] = {
        "checked": 0,
        "updated": 0,
        "unchanged": 0,
        "failed": 0,
        "skipped": 0,
        "tracked": 0,
        "unlimited": 0,
        "out_of_stock": 0,
        "hidden": 0,
        "retired": 0,
        "failures": [],
        "updates": [],
    }
    clients: dict[int, ZjmfFinanceClient] = {}

    for product, server in rows:
        upstream_pid = str(product.api_identifier or "").strip()
        if not upstream_pid:
            summary["skipped"] += 1
            continue

        client = clients.get(server.id)
        if client is None:
            client = _client_for_server(server)
            clients[server.id] = client

        try:
            raw_config = await client.get_product_config(upstream_pid)
            desired = _desired_stock(raw_config)
            summary["checked"] += 1
            if desired["stock_policy"] == StockPolicy.TRACKED.value:
                summary["tracked"] += 1
                if int(desired["stock"] or 0) <= 0:
                    summary["out_of_stock"] += 1
            else:
                summary["unlimited"] += 1
            if desired["hidden_enabled"]:
                summary["hidden"] += 1
            if desired["retired_enabled"]:
                summary["retired"] += 1

            changed = (
                _enum_value(product.stock_policy) != desired["stock_policy"]
                or int(product.stock or 0) != int(desired["stock"] or 0)
            )
            if changed:
                old_stock_policy = _enum_value(product.stock_policy)
                old_stock = int(product.stock or 0)
                product.stock_policy = desired["stock_policy"]
                product.stock = int(desired["stock"] or 0)
                summary["updated"] += 1
                if len(summary["updates"]) < 30:
                    summary["updates"].append(
                        {
                            "product_id": product.id,
                            "name": product.name,
                            "upstream_pid": upstream_pid,
                            "stock_policy": {"old": old_stock_policy, "new": product.stock_policy},
                            "stock": {"old": old_stock, "new": product.stock},
                            "hidden": desired["hidden_enabled"],
                            "retired": desired["retired_enabled"],
                        }
                    )
            else:
                summary["unchanged"] += 1
        except ZjmfFinanceError as exc:
            summary["failed"] += 1
            log.warning("zjmf stock sync failed: product=%s pid=%s error=%s", product.id, upstream_pid, exc.message)
            if len(summary["failures"]) < 30:
                summary["failures"].append(
                    {
                        "product_id": product.id,
                        "name": product.name,
                        "upstream_pid": upstream_pid,
                        "error": exc.message,
                    }
                )
        except Exception as exc:  # noqa: BLE001
            summary["failed"] += 1
            log.exception("zjmf stock sync unexpected error: product=%s pid=%s", product.id, upstream_pid)
            if len(summary["failures"]) < 30:
                summary["failures"].append(
                    {
                        "product_id": product.id,
                        "name": product.name,
                        "upstream_pid": upstream_pid,
                        "error": str(exc),
                    }
                )

    return summary
