import datetime
from typing import Dict, List, Tuple
import logging
import uuid

import requests

from pypos.core.base_service import BaseService
from pypos.core.utils.http_json_utils import parse_json_response
from pypos.core.utils.device_utils import get_active_device_info, get_device_id
from pypos.modules.penjualan.config.penjualan_config import (
    PENJUALAN_ENDPOINT_KEYS,
    build_endpoint_url,
    get_penjualan_app_config,
    get_penjualan_endpoint_config,
    get_request_timeout,
    get_toko_id_from_config,
)
from pypos.modules.penjualan.services.error_envelope_service import ErrorEnvelopeService

LOGGER = logging.getLogger(__name__)


class TransaksiService(BaseService):
    def __init__(self, http_client=None):
        super().__init__(http_client=http_client or requests)

    # edited by glg
    def _generate_trace_id(self, scope: str, hint: str = "") -> str:
        prefix = self._safe_text(scope, "fp").lower().replace(" ", "_")
        timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")
        suffix = uuid.uuid4().hex[:8]
        cleaned_hint = self._safe_text(hint, "").replace(" ", "")
        if cleaned_hint:
            cleaned_hint = cleaned_hint[:24]
            return f"{prefix}-{cleaned_hint}-{timestamp[-12:]}-{suffix}"
        return f"{prefix}-{timestamp[-12:]}-{suffix}"

    # edited by glg
    def _as_non_negative_int(self, value, default=0):
        try:
            parsed = int(float(value))
        except (TypeError, ValueError):
            parsed = int(default or 0)
        return parsed if parsed >= 0 else int(default or 0)

    # edited by glg
    def _as_positive_int(self, value, default=0):
        parsed = self._as_non_negative_int(value, default=default)
        return parsed if parsed > 0 else int(default or 0)

    # edited by glg
    def _safe_text(self, value, default=""):
        if value is None:
            return str(default or "")
        text = str(value).strip()
        if text:
            return text
        return str(default or "")

    # edited by glg
    def _resolve_event_datetime(self, item: Dict) -> str:
        source = item if isinstance(item, dict) else {}
        raw = self._safe_text(source.get("date") or source.get("dtime"), "")
        if raw:
            return raw.replace("T", " ")[:19]
        return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    # edited by glg
    def _normalize_failure_payload(
        self,
        payload: Dict,
        fallback_reason: str = "",
        fallback_error_code: str = "",
        trace_id: str = "",
    ) -> Dict:
        return ErrorEnvelopeService.normalize_failure_payload(
            payload,
            fallback_reason=fallback_reason,
            fallback_error_code=fallback_error_code,
            trace_id=trace_id,
            code_prefix="FP",
        )

    # edited by glg
    def _extract_http_error_payload(self, exc, fallback_reason: str, trace_id: str = "") -> Dict:
        response = getattr(exc, "response", None)
        payload = {}
        if response is not None:
            try:
                parsed = parse_json_response(response, label="transaksi_service_http_error")
                if isinstance(parsed, dict):
                    payload = dict(parsed)
            except (TypeError, ValueError) as exc:
                LOGGER.debug("[FP_HTTP_ERROR_PARSE_PRIMARY] parse_json_response gagal: %s", exc)
                try:
                    parsed = response.json()
                    if isinstance(parsed, dict):
                        payload = dict(parsed)
                except (TypeError, ValueError) as exc2:
                    LOGGER.debug("[FP_HTTP_ERROR_PARSE_FALLBACK] response.json gagal: %s", exc2)
                    payload = {}

        payload = self._normalize_failure_payload(
            payload,
            fallback_reason=fallback_reason,
            fallback_error_code="FP_API_HTTP_ERROR",
            trace_id=trace_id,
        )
        status_code = getattr(response, "status_code", None)
        if status_code is not None:
            payload.setdefault("http_status", int(status_code))
        if not self._safe_text(payload.get("message"), ""):
            message_text = self._safe_text(getattr(response, "text", ""), "")
            if message_text:
                payload["message"] = message_text[:300]
        if trace_id and not self._safe_text(payload.get("trace_id"), ""):
            payload["trace_id"] = trace_id
        return payload

    # edited by glg
    def _build_free_produk_note(
        self,
        *,
        free_qty: int,
        free_produk_nama: str,
        produk_beli_jml: int,
        ori_produk_nama: str,
        explicit_note: str = "",
    ) -> str:
        note = self._safe_text(explicit_note, "")
        if note:
            return note
        qty = max(1, self._as_positive_int(free_qty, 1))
        beli_jml = max(1, self._as_positive_int(produk_beli_jml, 1))
        free_name = self._safe_text(free_produk_nama, "Produk Free")
        ori_name = self._safe_text(ori_produk_nama, "Produk Asal")
        return f"Free {qty}x {free_name} dari pembelian {beli_jml}x {ori_name}"

    # edited by glg
    def _build_case_fallback_url(self, url: str) -> str:
        text = str(url or "").strip()
        if "/ProDiskon/" in text:
            return text.replace("/ProDiskon/", "/proDiskon/")
        if "/proDiskon/" in text:
            return text.replace("/proDiskon/", "/ProDiskon/")
        return ""

    # edited by glg
    def _summarize_free_produk_rows(self, rows: List[Dict], max_items: int = 3) -> str:
        if not isinstance(rows, list) or not rows:
            return "-"
        parts = []
        for item in rows[: max(1, int(max_items or 1))]:
            row = item if isinstance(item, dict) else {}
            transaksi_id = self._as_non_negative_int(row.get("transaksi_id"), 0)
            produk_id = self._as_non_negative_int(row.get("produk_id"), 0)
            free_produk_id = self._as_non_negative_int(row.get("free_produk_id"), 0)
            free_qty = self._as_non_negative_int(row.get("free_qty"), 0)
            transaksi_no = self._safe_text(row.get("transaksi_no"), "-")
            parts.append(
                f"trx_id={transaksi_id}|trx_no={transaksi_no}|produk={produk_id}|free={free_produk_id}|qty={free_qty}"
            )
        return "; ".join(parts) if parts else "-"

    # edited by glg
    def _normalize_free_produk_row(
        self,
        item: Dict,
        default_toko_id,
    ) -> Tuple[Dict, str]:
        row = item if isinstance(item, dict) else {}
        diskon_id = self._as_non_negative_int(
            row.get("diskon_id"),
            self._as_non_negative_int(row.get("nomer_diskon"), 0),
        )
        nomer_diskon = self._as_non_negative_int(row.get("nomer_diskon"), diskon_id)
        produk_id = self._as_non_negative_int(row.get("produk_id"), 0)
        ori_produk_id = self._as_non_negative_int(
            row.get("ori_produk_id"),
            self._as_non_negative_int(row.get("source_produk_id"), produk_id),
        )
        free_produk_id = self._as_non_negative_int(row.get("free_produk_id"), 0)
        free_qty = self._as_non_negative_int(row.get("free_qty"), 0)
        produk_beli_jml = self._as_non_negative_int(
            row.get("produk_beli_jml"),
            self._as_non_negative_int(row.get("source_qty"), free_qty),
        )
        minim = self._as_non_negative_int(row.get("minim"), 1)
        transaksi_id = self._as_non_negative_int(row.get("transaksi_id"), 0)
        oleh_id = self._as_non_negative_int(row.get("oleh_id"), 0)
        customer_id = self._as_non_negative_int(row.get("customer_id"), 1)
        kelipatan = self._as_non_negative_int(row.get("kelipatan"), 1)
        quota_global = self._as_non_negative_int(row.get("quota_global"), 0)
        quota_used = self._as_non_negative_int(row.get("quota_used"), 0)
        quota_sisa = self._as_non_negative_int(
            row.get("quota_sisa"),
            max(quota_global - quota_used, 0) if quota_global > 0 else 0,
        )
        row_toko_id = row.get("toko_id")
        # edited by glg
        # Fallback ke default_toko_id juga untuk nilai 0/"0" agar payload free produk tetap valid.
        if self._as_positive_int(row_toko_id, 0) <= 0:
            row_toko_id = default_toko_id
        toko_id = self._as_non_negative_int(row_toko_id, 0)

        produk_nama = self._safe_text(row.get("produk_nama"), "")
        ori_produk_nama = self._safe_text(
            row.get("ori_produk_nama") or row.get("source_produk_nama"),
            produk_nama,
        )
        free_produk_nama = self._safe_text(row.get("free_produk_nama"), "")
        transaksi_no = self._safe_text(row.get("transaksi_no"), "")
        oleh_nama = self._safe_text(row.get("oleh_nama"), "")
        customer_nama = self._safe_text(row.get("customer_nama"), "Tunai")
        dtime = self._resolve_event_datetime(row)
        free_produk_note = self._build_free_produk_note(
            free_qty=free_qty,
            free_produk_nama=free_produk_nama,
            produk_beli_jml=produk_beli_jml,
            ori_produk_nama=ori_produk_nama,
            explicit_note=self._safe_text(row.get("free_produk_note"), ""),
        )

        if diskon_id <= 0:
            return {}, "invalid_diskon_id"
        if produk_id <= 0:
            return {}, "invalid_produk_id"
        if free_produk_id <= 0:
            return {}, "invalid_free_produk_id"
        if free_qty <= 0:
            return {}, "invalid_free_qty"
        if not produk_nama:
            return {}, "missing_produk_nama"
        if not free_produk_nama:
            return {}, "missing_free_produk_nama"
        if transaksi_id <= 0:
            return {}, "invalid_transaksi_id"
        if not transaksi_no:
            return {}, "missing_transaksi_no"
        if toko_id <= 0:
            return {}, "missing_toko_id"
        if oleh_id <= 0:
            return {}, "invalid_oleh_id"
        if not oleh_nama:
            return {}, "missing_oleh_nama"
        if customer_id <= 0:
            return {}, "invalid_customer_id"
        if nomer_diskon <= 0:
            nomer_diskon = diskon_id
        if ori_produk_id <= 0:
            ori_produk_id = produk_id
        if minim <= 0:
            minim = 1
        if produk_beli_jml <= 0:
            produk_beli_jml = max(1, free_qty)

        return {
            "diskon_id": diskon_id,
            "nomer_diskon": nomer_diskon,
            "produk_id": produk_id,
            "produk_nama": produk_nama,
            "ori_produk_id": ori_produk_id,
            "ori_produk_nama": ori_produk_nama,
            "free_produk_id": free_produk_id,
            "free_produk_nama": free_produk_nama,
            "free_qty": free_qty,
            "produk_beli_jml": produk_beli_jml,
            "minim": minim,
            "kelipatan": kelipatan if kelipatan > 0 else 1,
            "quota_global": quota_global,
            "quota_used": quota_used,
            "quota_sisa": quota_sisa,
            "free_produk_note": free_produk_note,
            "date": dtime,
            "dtime": dtime,
            "settlement": 1,
            "transaksi_id": transaksi_id,
            "transaksi_no": transaksi_no,
            "toko_id": toko_id,
            "oleh_id": oleh_id,
            "oleh_nama": oleh_nama,
            "customer_id": customer_id,
            "customer_nama": customer_nama,
        }, ""

    def _resolve_toko_id(self, app_cfg, device_info=None):
        info = device_info if isinstance(device_info, dict) else (get_active_device_info(get_device_id()) or {})
        toko_id = info.get("toko_id")
        if toko_id in (None, ""):
            toko_id = get_toko_id_from_config(default=(app_cfg or {}).get("toko_id"))
        try:
            toko_id_int = int(toko_id)
        except (TypeError, ValueError):
            toko_id_int = 0
        return toko_id_int if toko_id_int > 0 else None

    def _http_post_json(self, url, data, timeout, files=None, trace_id="", endpoint_name="free_produk"):
        def _do_post(target_url):
            multipart_mode = files is not None
            data_len = len(data) if isinstance(data, dict) else 0
            files_len = len(files) if isinstance(files, (list, tuple)) else 0
            LOGGER.info(
                "[FreeProdukAPI][%s] POST endpoint=%s url=%s timeout=%s multipart=%s data_fields=%s file_fields=%s",
                trace_id or "-",
                endpoint_name or "-",
                target_url,
                timeout,
                int(bool(multipart_mode)),
                data_len,
                files_len,
            )
            try:
                request_kwargs = {
                    "data": data,
                    "timeout": timeout,
                    "retry_on": (requests.RequestException,),
                }
                if trace_id:
                    request_kwargs["headers"] = {"X-Trace-Id": trace_id}
                if files is not None:
                    request_kwargs["files"] = files
                resp = self.request_with_retry(
                    "POST",
                    target_url,
                    **request_kwargs,
                )
                try:
                    parsed = parse_json_response(resp, label="transaksi_service")
                    if isinstance(parsed, dict):
                        if int(parsed.get("status") or 0) != 1:
                            parsed = self._normalize_failure_payload(parsed, trace_id=trace_id)
                        LOGGER.info(
                            "[FreeProdukAPI][%s] RESP endpoint=%s url=%s status=%s reason=%s",
                            trace_id or "-",
                            endpoint_name or "-",
                            target_url,
                            parsed.get("status"),
                            parsed.get("reason", ""),
                        )
                        return parsed
                    LOGGER.warning(
                        "[FreeProdukAPI][%s] RESP endpoint=%s invalid_json_type url=%s",
                        trace_id or "-",
                        endpoint_name or "-",
                        target_url,
                    )
                    return self._normalize_failure_payload(
                        {"status": 0},
                        fallback_reason="invalid_json",
                        fallback_error_code="FP_API_INVALID_JSON",
                        trace_id=trace_id,
                    )
                except (TypeError, ValueError) as exc:
                    LOGGER.warning(
                        "[FreeProdukAPI][%s] RESP endpoint=%s invalid_json_parse url=%s err=%s",
                        trace_id or "-",
                        endpoint_name or "-",
                        target_url,
                        exc,
                    )
                    return self._normalize_failure_payload(
                        {"status": 0},
                        fallback_reason="invalid_json",
                        fallback_error_code="FP_API_INVALID_JSON",
                        trace_id=trace_id,
                    )
            except requests.exceptions.Timeout:
                LOGGER.warning(
                    "[FreeProdukAPI][%s] timeout endpoint=%s url=%s",
                    trace_id or "-",
                    endpoint_name or "-",
                    target_url,
                )
                return self._normalize_failure_payload(
                    {
                        "status": 0,
                        "message": (
                            f"Endpoint {self._safe_text(endpoint_name, 'api')} timeout. "
                            "Silakan coba lagi."
                        ),
                    },
                    fallback_reason="timeout",
                    fallback_error_code="FP_API_TIMEOUT",
                    trace_id=trace_id,
                )
            except requests.exceptions.ConnectionError:
                LOGGER.warning(
                    "[FreeProdukAPI][%s] connection_error endpoint=%s url=%s",
                    trace_id or "-",
                    endpoint_name or "-",
                    target_url,
                )
                return self._normalize_failure_payload(
                    {
                        "status": 0,
                        "message": (
                            f"Endpoint {self._safe_text(endpoint_name, 'api')} tidak dapat dihubungi. "
                            "Periksa koneksi server."
                        ),
                    },
                    fallback_reason="connection_error",
                    fallback_error_code="FP_API_CONNECTION_ERROR",
                    trace_id=trace_id,
                )
            except requests.exceptions.HTTPError as e:
                code = getattr(getattr(e, "response", None), "status_code", "unknown")
                payload = self._extract_http_error_payload(
                    e,
                    fallback_reason=f"http_error_{code}",
                    trace_id=trace_id,
                )
                LOGGER.warning(
                    "[FreeProdukAPI][%s] http_error endpoint=%s url=%s code=%s reason=%s error=%s",
                    trace_id or "-",
                    endpoint_name or "-",
                    target_url,
                    code,
                    payload.get("reason"),
                    payload.get("error", ""),
                )
                if not self._safe_text(payload.get("message"), ""):
                    payload["message"] = (
                        f"Endpoint {self._safe_text(endpoint_name, 'api')} merespons HTTP {code}."
                    )
                return payload
            except requests.exceptions.RequestException as e:
                LOGGER.warning(
                    "[FreeProdukAPI][%s] request_exception endpoint=%s url=%s error=%s",
                    trace_id or "-",
                    endpoint_name or "-",
                    target_url,
                    e,
                )
                return self._normalize_failure_payload(
                    {"status": 0},
                    fallback_reason=str(e),
                    fallback_error_code="FP_API_REQUEST_ERROR",
                    trace_id=trace_id,
                )
            except (RuntimeError, TypeError, ValueError, AttributeError) as e:
                LOGGER.exception(
                    "[FreeProdukAPI][%s] runtime_error endpoint=%s url=%s",
                    trace_id or "-",
                    endpoint_name or "-",
                    target_url,
                )
                return self._normalize_failure_payload(
                    {"status": 0},
                    fallback_reason=f"runtime_error:{str(e)}",
                    fallback_error_code="FP_API_UNEXPECTED_ERROR",
                    trace_id=trace_id,
                )

        result = _do_post(url)
        if isinstance(result, dict) and result.get("reason") == "http_error_404":
            fallback_url = self._build_case_fallback_url(url)
            if fallback_url and fallback_url != url:
                LOGGER.warning(
                    "[FreeProdukAPI][%s] fallback_endpoint_case endpoint=%s from=%s to=%s",
                    trace_id or "-",
                    endpoint_name or "-",
                    url,
                    fallback_url,
                )
                fallback_result = _do_post(fallback_url)
                if isinstance(fallback_result, dict):
                    if int(fallback_result.get("status") or 0) == 1:
                        LOGGER.info(
                            "[FreeProdukAPI][%s] fallback_endpoint_case endpoint=%s success to=%s",
                            trace_id or "-",
                            endpoint_name or "-",
                            fallback_url,
                        )
                        return fallback_result
                    if str(fallback_result.get("reason") or "") != "http_error_404":
                        LOGGER.warning(
                            "[FreeProdukAPI][%s] fallback_endpoint_case endpoint=%s resolved_non404 reason=%s",
                            trace_id or "-",
                            endpoint_name or "-",
                            fallback_result.get("reason"),
                        )
                        return fallback_result
        if isinstance(result, dict) and trace_id:
            result.setdefault("trace_id", trace_id)
        return result

    def cek_kuota_free_produk(self, barang, user_info=None):
        trace_id = self._generate_trace_id("fpq", str((barang or {}).get("id") or ""))
        endpoint_cfg = get_penjualan_endpoint_config()
        app_cfg = get_penjualan_app_config()
        url, url_error = build_endpoint_url(
            PENJUALAN_ENDPOINT_KEYS["ep_diskon_check_free_produk"],
            endpoint_cfg=endpoint_cfg,
        )
        timeout = get_request_timeout()
        if url_error:
            LOGGER.warning("[FreeProdukAPI][%s] check_quota url_error=%s", trace_id, url_error)
            return self._normalize_failure_payload(
                {"status": 0},
                fallback_reason=str(url_error),
                fallback_error_code="FP_ENDPOINT_NOT_READY",
                trace_id=trace_id,
            )
        if not barang or not barang.get("id"):
            LOGGER.warning("[FreeProdukAPI][%s] check_quota invalid_barang", trace_id)
            return self._normalize_failure_payload(
                {"status": 0},
                fallback_reason="invalid_barang",
                fallback_error_code="FP_INVALID_BARANG",
                trace_id=trace_id,
            )

        device_info = get_active_device_info(get_device_id()) or {}
        toko_id = self._resolve_toko_id(app_cfg, device_info=device_info)
        if not toko_id:
            LOGGER.warning("[FreeProdukAPI][%s] check_quota missing_toko_id", trace_id)
            return self._normalize_failure_payload(
                {"status": 0},
                fallback_reason="missing_toko_id",
                fallback_error_code="FP_MISSING_TOKO_ID",
                trace_id=trace_id,
            )

        barang_data = barang if isinstance(barang, dict) else {}
        produk_id = self._as_non_negative_int(
            barang_data.get("ori_produk_id"),
            self._as_non_negative_int(barang_data.get("id"), 0),
        )
        free_produk_id = self._as_non_negative_int(barang_data.get("free_produk_id"), 0)
        free_qty = self._as_non_negative_int(
            barang_data.get("jumlah_free"),
            self._as_non_negative_int(barang_data.get("free_qty"), 0),
        )
        produk_beli_jml = self._as_non_negative_int(
            barang_data.get("produk_beli_jml"),
            self._as_non_negative_int(barang_data.get("jumlah"), free_qty),
        )
        diskon_id = self._as_non_negative_int(
            barang_data.get("diskon_id"),
            self._as_non_negative_int(barang_data.get("nomer_diskon"), 0),
        )
        nomer_diskon = self._as_non_negative_int(barang_data.get("nomer_diskon"), diskon_id)
        kelipatan = self._as_non_negative_int(barang_data.get("kelipatan"), 1)
        minim = self._as_non_negative_int(barang_data.get("minim"), 1)
        quota_global = self._as_non_negative_int(barang_data.get("quota_global"), 0)
        quota_used = self._as_non_negative_int(barang_data.get("quota_used"), 0)
        quota_sisa = self._as_non_negative_int(
            barang_data.get("quota_sisa"),
            max(quota_global - quota_used, 0) if quota_global > 0 else 0,
        )
        produk_nama = self._safe_text(
            barang_data.get("ori_produk_nama") or barang_data.get("nama"),
            "",
        )
        free_produk_nama = self._safe_text(barang_data.get("free_produk_nama"), "")
        if free_qty <= 0:
            free_qty = 1
        if produk_beli_jml <= 0:
            produk_beli_jml = max(1, free_qty)
        if minim <= 0:
            minim = 1
        if nomer_diskon <= 0:
            nomer_diskon = diskon_id
        free_produk_note = self._build_free_produk_note(
            free_qty=free_qty,
            free_produk_nama=free_produk_nama,
            produk_beli_jml=produk_beli_jml,
            ori_produk_nama=produk_nama or free_produk_nama,
            explicit_note=self._safe_text(barang_data.get("free_produk_note"), ""),
        )
        now_dtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        data = {
            "diskon_id": diskon_id,
            "nomer_diskon": nomer_diskon,
            "produk_id": produk_id,
            "produk_nama": produk_nama,
            "ori_produk_id": produk_id,
            "ori_produk_nama": produk_nama,
            "free_produk_id": free_produk_id,
            "free_produk_nama": free_produk_nama,
            "free_qty": free_qty,
            "produk_beli_jml": produk_beli_jml,
            "kelipatan": kelipatan if kelipatan > 0 else 1,
            "minim": minim,
            "quota_global": quota_global,
            "quota_used": quota_used,
            "quota_sisa": quota_sisa,
            "free_produk_note": free_produk_note,
            "date": now_dtime,
            "dtime": now_dtime,
            "settlement": 1,
            "transaksi_id": "",
            "transaksi_no": "",
            "toko_id": toko_id,
            "oleh_id": 0,
            "oleh_nama": "-",
            "customer_id": 1,
            "customer_nama": "Tunai",
        }
        if user_info:
            data["oleh_id"] = user_info.get("id", data["oleh_id"])
            data["oleh_nama"] = user_info.get("nama", data["oleh_nama"])
        LOGGER.info(
            "[FreeProdukAPI][%s] check_quota request produk_id=%s free_produk_id=%s toko_id=%s",
            trace_id,
            data.get("produk_id"),
            data.get("free_produk_id"),
            data.get("toko_id"),
        )
        return self._http_post_json(
            url,
            data,
            timeout,
            trace_id=trace_id,
            endpoint_name="check_free_quota",
        )

    def update_quota_free_produk(self, arr_free_produk):
        trace_hint = ""
        if isinstance(arr_free_produk, list) and arr_free_produk:
            trace_hint = str((arr_free_produk[0] or {}).get("transaksi_id") or "")
        trace_id = self._generate_trace_id("fps", trace_hint)
        if not arr_free_produk:
            LOGGER.warning("[FreeProdukAPI][%s] save_free_produk empty_payload", trace_id)
            return self._normalize_failure_payload(
                {"status": 0},
                fallback_reason="empty_payload",
                fallback_error_code="FP_EMPTY_PAYLOAD",
                trace_id=trace_id,
            )

        endpoint_cfg = get_penjualan_endpoint_config()
        app_cfg = get_penjualan_app_config()
        url, url_error = build_endpoint_url(
            PENJUALAN_ENDPOINT_KEYS["ep_diskon_save_free_produk"],
            endpoint_cfg=endpoint_cfg,
        )
        if url_error:
            LOGGER.warning("[FreeProdukAPI][%s] save_free_produk url_error=%s", trace_id, url_error)
            return self._normalize_failure_payload(
                {"status": 0},
                fallback_reason=str(url_error),
                fallback_error_code="FP_ENDPOINT_NOT_READY",
                trace_id=trace_id,
            )

        device_info = get_active_device_info(get_device_id()) or {}
        default_toko_id = self._resolve_toko_id(app_cfg, device_info=device_info)
        LOGGER.info(
            "[FreeProdukAPI][%s] save_free_produk start total_rows=%s sample=%s",
            trace_id,
            len(arr_free_produk) if isinstance(arr_free_produk, list) else 0,
            self._summarize_free_produk_rows(arr_free_produk if isinstance(arr_free_produk, list) else []),
        )
        form_data = {}
        invalid_rows: List[str] = []
        valid_row_count = 0

        for idx, item in enumerate(arr_free_produk):
            row_payload, err = self._normalize_free_produk_row(item, default_toko_id)
            if err:
                invalid_rows.append(f"{idx}:{err}")
                continue
            prefix = f"arr[{valid_row_count}]"
            valid_row_count += 1
            for key, value in row_payload.items():
                form_data[f"{prefix}[{key}]"] = value

        if valid_row_count <= 0:
            reason = "invalid_payload"
            if invalid_rows:
                reason = f"{reason}:{'|'.join(invalid_rows[:3])}"
            LOGGER.warning(
                "[FreeProdukAPI][%s] save_free_produk no_valid_rows total_rows=%s invalid_samples=%s",
                trace_id,
                len(arr_free_produk) if isinstance(arr_free_produk, list) else 0,
                "|".join(invalid_rows[:3]),
            )
            return self._normalize_failure_payload(
                {"status": 0},
                fallback_reason=reason,
                fallback_error_code="FP_INVALID_PAYLOAD",
                trace_id=trace_id,
            )

        if invalid_rows:
            LOGGER.warning(
                "[FreeProdukAPI][%s] save_free_produk partial_valid valid_rows=%s invalid_rows=%s invalid_samples=%s",
                trace_id,
                valid_row_count,
                len(invalid_rows),
                "|".join(invalid_rows[:3]),
            )
        else:
            LOGGER.info("[FreeProdukAPI][%s] save_free_produk all_rows_valid count=%s", trace_id, valid_row_count)

        # edited by glg
        # Kontrak tutorial free produk mewajibkan multipart/form-data.
        multipart_files = [
            (field, (None, self._safe_text(value, "")))
            for field, value in form_data.items()
        ]
        timeout = get_request_timeout()
        result = self._http_post_json(
            url,
            data={},
            files=multipart_files,
            timeout=timeout,
            trace_id=trace_id,
            endpoint_name="save_free_produk",
        )
        if isinstance(result, dict) and int(result.get("status") or 0) != 1:
            result = self._normalize_failure_payload(result, trace_id=trace_id)
        if isinstance(result, dict) and invalid_rows:
            warning_text = f"skip_invalid_rows:{'|'.join(invalid_rows[:3])}"
            if int(result.get("status") or 0) == 1:
                result.setdefault("warning", warning_text)
            else:
                base_reason = str(result.get("reason") or "invalid_payload")
                result["reason"] = f"{base_reason}|{warning_text}"
        if isinstance(result, dict):
            result.setdefault("trace_id", trace_id)
        if isinstance(result, dict) and int(result.get("status") or 0) == 1:
            LOGGER.info(
                "[FreeProdukAPI][%s] save_free_produk success valid_rows=%s warning=%s",
                trace_id,
                valid_row_count,
                result.get("warning", ""),
            )
        else:
            LOGGER.warning(
                "[FreeProdukAPI][%s] save_free_produk failed valid_rows=%s reason=%s",
                trace_id,
                valid_row_count,
                (result or {}).get("reason") if isinstance(result, dict) else str(result),
            )
        return result
