# edited by glg
import logging

import requests

from pypos.core.base_service import BaseService
from pypos.core.utils.config_utils import read_app_settings, save_app_settings
from pypos.core.utils.device_utils import get_active_device_info, get_device_id
from pypos.core.utils.http_json_utils import parse_json_response
from pypos.modules.penjualan.config.penjualan_config import (
    PENJUALAN_ENDPOINT_KEYS,
    build_endpoint_url,
    get_penjualan_endpoint_config,
    get_request_timeout,
)


LOGGER = logging.getLogger(__name__)


class PpnServerSettingsService(BaseService):
    DEFAULT_PPN_MODE = "include"
    DEFAULT_PPN_PERCENT = 11

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

    def _to_percent_int(self, value):
        try:
            parsed = int(float(value))
        except (TypeError, ValueError, OverflowError):
            return None
        if parsed < 0 or parsed > 100:
            return None
        return parsed

    def _to_status_int(self, value, default=0):
        try:
            return int(float(value))
        except (TypeError, ValueError, OverflowError):
            return int(default)

    def _normalize_mode(self, value, default="include"):
        mode = str(value or "").strip().lower()
        if mode in {"include", "exclude"}:
            return mode
        return str(default or "include").strip().lower() or "include"

    def _extract_ppn_percent(self, payload, data_node=None):
        stack = []
        if isinstance(data_node, dict):
            stack.append(data_node)
        if isinstance(payload, dict):
            stack.append(payload)
            for key in ("data", "result", "setting", "settings", "config"):
                nested = payload.get(key)
                if isinstance(nested, dict):
                    stack.append(nested)
        for node in stack:
            for key in ("ppn_percent", "ppn", "persen_ppn", "pajak_percent", "tax_percent"):
                percent = self._to_percent_int(node.get(key))
                if percent is not None:
                    return percent
        return None

    def _resolve_contract_response(self, payload):
        body = payload if isinstance(payload, dict) else {}
        data_node = body.get("data")
        if not isinstance(data_node, dict):
            data_node = {}

        trace_id = str(body.get("trace_id") or "").strip()
        has_status = "status" in body
        status = self._to_status_int(body.get("status"), 0 if has_status else 1)
        reason = str(body.get("reason") or "").strip()
        message = str(body.get("message") or "").strip()

        if has_status and status != 1:
            return False, reason or "server_reject", {
                "trace_id": trace_id,
                "message": message,
            }

        mode_raw = data_node.get("ppn_mode")
        if mode_raw in (None, ""):
            mode_raw = body.get("ppn_mode")
        mode = self._normalize_mode(mode_raw, default=self.DEFAULT_PPN_MODE)

        ppn_percent = self._extract_ppn_percent(body, data_node=data_node)
        if ppn_percent is None:
            return False, "invalid_payload_ppn_percent", {
                "trace_id": trace_id,
                "message": message,
                "raw_keys": list(body.keys())[:12],
            }

        return True, "ok", {
            "trace_id": trace_id,
            "ppn_mode": mode,
            "ppn_percent": int(ppn_percent),
        }

    def _build_identity_params(self):
        machine_id = str(get_device_id() or "").strip()
        info = get_active_device_info(machine_id) or {}
        cabang_id_raw = info.get("cabang_id")
        try:
            cabang_id = int(cabang_id_raw)
        except (TypeError, ValueError, OverflowError):
            cabang_id = 0
        params = {}
        if machine_id:
            params["machine_id"] = machine_id
        if cabang_id > 0:
            params["cabang_id"] = cabang_id
        return params

    def get_endpoint_status(self):
        endpoint_cfg = get_penjualan_endpoint_config()
        endpoint_name = PENJUALAN_ENDPOINT_KEYS.get("ep_ppn_settings")
        if not endpoint_name:
            return {"ready": False, "reason": "missing_endpoint_key", "url": ""}
        url, reason = build_endpoint_url(endpoint_name, endpoint_cfg=endpoint_cfg)
        if reason:
            return {"ready": False, "reason": str(reason), "url": ""}
        return {"ready": True, "reason": "", "url": str(url or "")}

    def fetch_server_setting(self):
        endpoint_status = self.get_endpoint_status()
        if not endpoint_status.get("ready"):
            return False, str(endpoint_status.get("reason") or "endpoint_not_ready"), {}

        url = str(endpoint_status.get("url") or "").strip()
        timeout_sec = max(1, min(int(get_request_timeout(default=3)), 5))
        params = self._build_identity_params()
        try:
            response = self.request_with_retry(
                "GET",
                url,
                params=params,
                timeout=timeout_sec,
                retries=0,
                retry_on=(requests.RequestException,),
                auth_required=True,
            )
            payload = parse_json_response(response, label="ppn_server_settings")
            if not isinstance(payload, dict):
                return False, "invalid_json", {}
            ok_contract, reason, parsed = self._resolve_contract_response(payload)
            if not ok_contract:
                return False, str(reason or "invalid_payload"), parsed or {}
            return True, "ok", {
                "ppn_percent": int(parsed.get("ppn_percent") or 0),
                "ppn_mode": self._normalize_mode(parsed.get("ppn_mode"), default=self.DEFAULT_PPN_MODE),
                "trace_id": str(parsed.get("trace_id") or ""),
                "source_url": url,
            }
        except requests.exceptions.Timeout:
            return False, "timeout", {}
        except requests.exceptions.ConnectionError:
            return False, "connection_error", {}
        except requests.exceptions.HTTPError as exc:
            code = getattr(getattr(exc, "response", None), "status_code", "unknown")
            return False, f"http_error_{code}", {}
        except (requests.RequestException, TypeError, ValueError, KeyError, AttributeError) as exc:
            return False, f"unexpected_error:{exc}", {}

    def _apply_setting(self, mode, percent):
        target_mode = self._normalize_mode(mode, default=self.DEFAULT_PPN_MODE)
        target_percent = self._to_percent_int(percent)
        if target_percent is None:
            target_percent = int(self.DEFAULT_PPN_PERCENT)

        current_cfg = read_app_settings() or {}
        current_mode, current_percent = self._read_current_setting(current_cfg)

        changed = not (
            current_mode == target_mode
            and int(current_percent) == int(target_percent)
        )
        if changed:
            save_app_settings(
                {
                    "ppn_mode": target_mode,
                    "ppn_percent": int(target_percent),
                }
            )
        return bool(changed), target_mode, int(target_percent)

    def _read_current_setting(self, cfg=None):
        current_cfg = cfg if isinstance(cfg, dict) else (read_app_settings() or {})
        current_mode = self._normalize_mode(
            current_cfg.get("ppn_mode"),
            default=self.DEFAULT_PPN_MODE,
        )
        current_percent = self._to_percent_int(current_cfg.get("ppn_percent"))
        if current_percent is None:
            current_percent = int(self.DEFAULT_PPN_PERCENT)
        return current_mode, int(current_percent)

    def _apply_fallback_default(self, trigger_reason="", payload=None):
        changed, mode, percent = self._apply_setting(
            self.DEFAULT_PPN_MODE,
            self.DEFAULT_PPN_PERCENT,
        )
        reason_text = str(trigger_reason or "").strip() or "fallback_default"
        payload_map = payload if isinstance(payload, dict) else {}
        trace_id = str(payload_map.get("trace_id") or "").strip()
        soft_reasons = {
            "missing_api_base_url",
            "missing_ep_ppn_settings",
            "endpoint_not_ready",
            "server_reject",
            "setting_not_found",
            "http_error_404",
        }
        log_method = LOGGER.info if reason_text in soft_reasons else LOGGER.warning
        log_method(
            "[PPN_SERVER] Fallback ke mode=%s persen=%s (reason=%s)",
            mode,
            percent,
            reason_text,
        )
        return {
            "status": 1,
            "reason": "fallback_include_11",
            "source": "fallback",
            "fallback_reason": reason_text,
            "mode": mode,
            "ppn_percent": percent,
            "changed": bool(changed),
            "trace_id": trace_id,
            "source_url": "",
        }

    def _keep_current_nonfatal(self, trigger_reason="", payload=None):
        mode, percent = self._read_current_setting()
        payload_map = payload if isinstance(payload, dict) else {}
        reason_text = str(trigger_reason or "").strip() or "nonfatal_error"
        trace_id = str(payload_map.get("trace_id") or "").strip()
        LOGGER.warning(
            "[PPN_SERVER] Endpoint gagal sementara. Pertahankan setting saat ini mode=%s persen=%s reason=%s",
            mode,
            percent,
            reason_text,
        )
        return {
            "status": 1,
            "reason": "fallback_keep_current",
            "source": "fallback",
            "fallback_reason": reason_text,
            "mode": mode,
            "ppn_percent": int(percent),
            "changed": False,
            "trace_id": trace_id,
            "source_url": "",
        }

    def sync_if_configured(self):
        ok, reason, payload = self.fetch_server_setting()
        if not ok:
            reason_text = str(reason or "").strip()
            no_rule_reasons = {
                "missing_api_base_url",
                "missing_ep_ppn_settings",
                "endpoint_not_ready",
                "setting_not_found",
                "http_error_404",
                "invalid_json",
                "invalid_payload",
                "invalid_payload_ppn_percent",
            }
            if reason_text in no_rule_reasons:
                return self._apply_fallback_default(trigger_reason=reason_text, payload=payload)
            return self._keep_current_nonfatal(trigger_reason=reason_text, payload=payload)

        changed, mode, ppn_percent = self._apply_setting(
            payload.get("ppn_mode"),
            payload.get("ppn_percent"),
        )
        if changed:
            LOGGER.info(
                "[PPN_SERVER] Sinkron setting pajak berhasil. mode=%s ppn_percent=%s url=%s",
                mode,
                ppn_percent,
                str(payload.get("source_url") or "-"),
            )
        else:
            LOGGER.debug(
                "[PPN_SERVER] Tidak ada perubahan setting pajak dari server. mode=%s ppn_percent=%s",
                mode,
                ppn_percent,
            )
        return {
            "status": 1,
            "reason": "ok",
            "source": "server",
            "mode": mode,
            "ppn_percent": ppn_percent,
            "changed": bool(changed),
            "trace_id": str(payload.get("trace_id") or ""),
            "source_url": str(payload.get("source_url") or ""),
        }
