"""Synology DSM 7.x API 클라이언트.

각 호출 = login → 작업 → logout (세션 풀링은 v1.1+에서). 단일 컨테이너 + 동시성 낮음 가정.

- create_share_link(file_path, expires_in_sec) -> share URL
"""
import asyncio
import logging
import os
from datetime import datetime, timedelta, timezone

import httpx

logger = logging.getLogger("packs-lab.dsm")

DSM_HOST = os.getenv("DSM_HOST", "")  # 예: https://gahusb.synology.me:5001
DSM_USER = os.getenv("DSM_USER", "")
DSM_PASS = os.getenv("DSM_PASS", "")
# LAN IP로 DSM 접근 시 self-signed cert가 IP에 매칭 안 되어 검증 실패. LAN 내부 통신이라 false 허용.
# 운영에서 LAN IP + self-signed면 DSM_VERIFY_SSL=false. 도메인 + 정상 cert면 기본값(true) 유지.
DSM_VERIFY_SSL = os.getenv("DSM_VERIFY_SSL", "true").strip().lower() != "false"
DSM_MAX_RETRIES = max(1, int(os.getenv("DSM_MAX_RETRIES", "3")))
DSM_BACKOFF_SEC = float(os.getenv("DSM_BACKOFF_SEC", "0.5"))

API_AUTH = "/webapi/auth.cgi"
API_SHARE = "/webapi/entry.cgi"


class DSMError(RuntimeError):
    pass


async def _request_with_retry(
    client: httpx.AsyncClient,
    url: str,
    params: dict,
    timeout: float,
) -> httpx.Response:
    """5xx · transport · timeout만 지수백오프 retry. 4xx와 DSM success=false는 호출자가 판단."""
    last_exc: Exception | None = None
    for attempt in range(DSM_MAX_RETRIES):
        try:
            r = await client.get(url, params=params, timeout=timeout)
            if r.status_code < 500:
                return r
            last_exc = httpx.HTTPStatusError(
                f"HTTP {r.status_code}", request=r.request, response=r
            )
            logger.warning(
                "DSM HTTP %s — attempt %s/%s body=%s",
                r.status_code, attempt + 1, DSM_MAX_RETRIES, r.text[:200],
            )
        except (httpx.TransportError, httpx.TimeoutException) as e:
            last_exc = e
            logger.warning(
                "DSM transport error: %s — attempt %s/%s",
                e, attempt + 1, DSM_MAX_RETRIES,
            )
        if attempt < DSM_MAX_RETRIES - 1:
            await asyncio.sleep(DSM_BACKOFF_SEC * (2 ** attempt))
    raise DSMError(f"DSM 요청 실패 (재시도 {DSM_MAX_RETRIES}회): {last_exc}")


async def _login(client: httpx.AsyncClient) -> str:
    """DSM 세션 sid 반환."""
    if not all([DSM_HOST, DSM_USER, DSM_PASS]):
        raise DSMError("DSM 환경변수 미설정")
    r = await _request_with_retry(
        client,
        f"{DSM_HOST}{API_AUTH}",
        {
            "api": "SYNO.API.Auth",
            "version": "7",
            "method": "login",
            "account": DSM_USER,
            "passwd": DSM_PASS,
            "session": "FileStation",
            "format": "sid",
        },
        15.0,
    )
    r.raise_for_status()
    data = r.json()
    if not data.get("success"):
        err = data.get("error", {})
        logger.error("DSM login 실패: code=%s error=%s", err.get("code"), err)
        raise DSMError(f"DSM login 실패: code={err.get('code')} error={err}")
    return data["data"]["sid"]


async def _logout(client: httpx.AsyncClient, sid: str) -> None:
    try:
        await client.get(
            f"{DSM_HOST}{API_AUTH}",
            params={
                "api": "SYNO.API.Auth",
                "version": "7",
                "method": "logout",
                "session": "FileStation",
                "_sid": sid,
            },
            timeout=10.0,
        )
    except Exception as e:
        logger.warning("DSM logout 실패: %s", e)


async def create_share_link(file_path: str, expires_in_sec: int = 14400) -> tuple[str, datetime]:
    """파일 공유 링크 생성. 반환: (URL, expires_at).

    file_path: NAS 절대경로 (예: /volume1/docker/webpage/media/packs/master/x.mp4)
    expires_in_sec: 만료 (기본 4시간)
    """
    expires_at = datetime.now(timezone.utc) + timedelta(seconds=expires_in_sec)
    expire_time_ms = int(expires_at.timestamp() * 1000)

    async with httpx.AsyncClient(verify=DSM_VERIFY_SSL) as client:
        sid = await _login(client)
        try:
            r = await _request_with_retry(
                client,
                f"{DSM_HOST}{API_SHARE}",
                {
                    "api": "SYNO.FileStation.Sharing",
                    "version": "3",
                    "method": "create",
                    "path": file_path,
                    "date_expired": expire_time_ms,
                    "_sid": sid,
                },
                15.0,
            )
            r.raise_for_status()
            data = r.json()
            if not data.get("success"):
                err = data.get("error", {})
                logger.error(
                    "DSM Sharing.create 실패: path=%s code=%s error=%s",
                    file_path, err.get("code"), err,
                )
                raise DSMError(f"DSM Sharing.create 실패: code={err.get('code')} error={err}")
            links = data["data"]["links"]
            if not links:
                raise DSMError("Sharing 응답에 링크 없음")
            url = links[0]["url"]
            logger.info("DSM share link created: path=%s", file_path)
            return url, expires_at
        finally:
            await _logout(client, sid)
