# -*- coding: utf-8 -*-
"""ig_sort.py — Instagram sort/server sync worker helper.

역할:
- yjm_win2rtc 안에서 서버/yellow.php/MySQL 연동만 담당한다.
- 마우스 클릭, hover 판단, md5 계산, UI 조작은 하지 않는다.
- test.py 레거시 진단 파일에서 API 호출 기능만 추출한 모듈이다.
"""

from __future__ import annotations

import json
import os
import socket
import urllib.error
import urllib.parse
import urllib.request
from typing import Any, Callable

DEFAULT_BASE_URL = "https://n.yjm.kr/i/yellow.php"
DEFAULT_TOKEN = "ABC1234XYZ"

LogFn = Callable[[str], None]


def default_user() -> str:
    return (os.environ.get("COMPUTERNAME") or socket.gethostname() or "local_pc").strip() or "local_pc"


def mask_secret(value: Any) -> str:
    s = str(value or "")
    if len(s) <= 8:
        return "****" if s else ""
    return s[:4] + "..." + s[-3:]


def _api_cfg(cfg: dict[str, Any] | None) -> dict[str, Any]:
    cfg = cfg if isinstance(cfg, dict) else {}
    api = cfg.get("yellow_api") if isinstance(cfg.get("yellow_api"), dict) else {}
    return {
        "enabled": bool(api.get("enabled", True)),
        "url": str(api.get("url") or DEFAULT_BASE_URL).strip(),
        "token": str(api.get("token") or DEFAULT_TOKEN).strip(),
        "timeout_sec": float(api.get("timeout_sec") or 10),
    }


def call_api(cfg: dict[str, Any] | None, mode: str, fields: dict[str, Any] | None = None, logger: LogFn | None = None) -> dict[str, Any]:
    """Call yellow.php.

    반환은 항상 dict. 네트워크 실패는 예외를 던지지 않고 ok=False로 반환한다.
    """
    logger = logger or (lambda msg: None)
    api = _api_cfg(cfg)
    if not api.get("enabled", True):
        logger(f"ig_sort: 서버 전송 꺼짐 mode={mode}")
        return {"ok": False, "skipped": True, "error": "yellow_api_disabled", "mode": mode}
    url = str(api.get("url") or "").strip()
    token = str(api.get("token") or "").strip()
    if not url:
        logger(f"ig_sort: 서버 URL 없음 mode={mode}")
        return {"ok": False, "error": "yellow_api_url_empty", "mode": mode}

    payload = {"mode": str(mode or ""), "token": token}
    payload.update({k: v for k, v in (fields or {}).items() if v is not None})
    log_payload = dict(payload)
    if "token" in log_payload:
        log_payload["token"] = mask_secret(log_payload.get("token"))

    body = urllib.parse.urlencode(payload).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=body,
        headers={"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"},
        method="POST",
    )
    def parse_response(raw: str, http_status: int | None) -> dict[str, Any]:
        try:
            parsed = json.loads(raw)
        except Exception:
            parsed = {
                "ok": False,
                "error": "bad_json_from_yellow",
                "raw": raw[:4000],
            }
        if not isinstance(parsed, dict):
            parsed = {"ok": False, "error": "bad_response", "raw": raw[:4000]}
        parsed.setdefault("mode", mode)
        parsed["http_status"] = http_status
        return parsed

    try:
        logger(f"ig_sort: 서버 요청 mode={mode} shortcode={payload.get('shortcode','')} asset={payload.get('asset_kind','')}")
        with urllib.request.urlopen(req, timeout=float(api.get("timeout_sec") or 10)) as res:
            raw = res.read().decode("utf-8", errors="replace")
            parsed = parse_response(raw, getattr(res, "status", None))
            logger(
                "ig_sort: 서버 응답 "
                f"mode={mode} ok={parsed.get('ok')} exists={parsed.get('exists','')} "
                f"status={parsed.get('http_status','')} stage={parsed.get('error_stage','')} error={parsed.get('error','')}"
            )
            return parsed
    except urllib.error.HTTPError as e:
        try:
            raw = e.read().decode("utf-8", errors="replace")
        except Exception:
            raw = ""
        parsed = parse_response(raw, int(getattr(e, "code", 0) or 0))
        server_error = str(parsed.get("error") or e.reason or e)
        result = {
            "ok": False,
            "error": server_error,
            "error_code": "yellow_http_error",
            "http_status": int(getattr(e, "code", 0) or 0),
            "mode": mode,
            "server_response": parsed,
        }
        for key in ("error_stage", "sql_error_code", "error_class", "message", "table", "missing_columns", "schema"):
            if key in parsed:
                result[key] = parsed.get(key)
        logger(
            "ig_sort: 서버 HTTP 실패 "
            f"mode={mode} status={result.get('http_status')} stage={result.get('error_stage','')} "
            f"error={server_error} message={str(result.get('message') or '')[:1000]} "
            f"missing={result.get('missing_columns','')}"
        )
        return result
    except Exception as e:
        logger(f"ig_sort: 서버 실패 mode={mode} error={e}")
        return {"ok": False, "error": str(e), "error_code": "yellow_api_error", "mode": mode}


__all__ = ["call_api", "default_user", "mask_secret", "DEFAULT_BASE_URL", "DEFAULT_TOKEN"]
