import json
import re
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional

# edited by glg


class TransactionExportValueUtils:
    _INT_RE = re.compile(r"^-?\d+$")

    @staticmethod
    def as_number_or_default(value, default=0):
        if value is None:
            return default
        if isinstance(value, (int, float)):
            return value
        text = str(value).strip()
        if text == "" or text.lower() in {"none", "null"}:
            return default
        try:
            if TransactionExportValueUtils._INT_RE.fullmatch(text):
                return int(text)
            return float(text)
        except (TypeError, ValueError, OverflowError):
            return default

    @staticmethod
    def as_positive_int(value, default=0) -> int:
        raw_default = int(default or 0)
        num = TransactionExportValueUtils.as_number_or_default(value, raw_default)
        try:
            parsed = int(float(num))
        except (TypeError, ValueError, OverflowError):
            parsed = raw_default
        return parsed if parsed > 0 else 0

    @staticmethod
    def is_blank_value(value):
        if value is None:
            return True
        text = str(value).strip().lower()
        return text == "" or text == "none" or text == "null"

    @staticmethod
    def to_int(value, default: int = 0) -> int:
        try:
            if value is None:
                return int(default)
            if isinstance(value, bool):
                return int(value)
            if isinstance(value, (int, float)):
                return int(round(float(value)))
            text = str(value).strip()
            if not text or text.lower() in {"none", "null"}:
                return int(default)
            if "," in text and "." not in text:
                text = text.replace(",", ".")
            return int(round(float(text)))
        except (TypeError, ValueError, OverflowError):
            return int(default)

    @staticmethod
    def to_float(value, default: float = 0.0) -> float:
        try:
            if value is None:
                return float(default)
            if isinstance(value, bool):
                return float(int(value))
            if isinstance(value, (int, float)):
                return float(value)
            text = str(value).strip()
            if not text or text.lower() in {"none", "null"}:
                return float(default)
            if "," in text and "." not in text:
                text = text.replace(",", ".")
            return float(text)
        except (TypeError, ValueError, OverflowError):
            return float(default)

    @staticmethod
    def parse_semicolon_log(raw_text: Any) -> Dict[str, str]:
        parsed: Dict[str, str] = {}
        text = str(raw_text or "").strip()
        if not text:
            return parsed
        for segment in text.split(";"):
            part = str(segment or "").strip()
            if not part or "=" not in part:
                continue
            key, val = part.split("=", 1)
            key = str(key or "").strip()
            if not key:
                continue
            parsed[key] = str(val or "").strip()
        return parsed

    @staticmethod
    def normalize_dtime_value(raw_value: Any, fallback_value: str) -> str:
        text = str(raw_value or "").strip()
        if text:
            return text
        return str(fallback_value or datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

    @staticmethod
    def parse_int_list(
        raw_value: Any,
        *,
        to_int_fn: Optional[Callable[[Any, int], int]] = None,
        blank_checker: Optional[Callable[[Any], bool]] = None,
    ) -> List[int]:
        safe_to_int = to_int_fn or TransactionExportValueUtils.to_int
        is_blank = blank_checker or TransactionExportValueUtils.is_blank_value

        values: List[Any]
        if isinstance(raw_value, (list, tuple, set)):
            values = list(raw_value)
        elif is_blank(raw_value):
            values = []
        else:
            text = str(raw_value).strip()
            try:
                parsed = json.loads(text)
            except (ValueError, TypeError, json.JSONDecodeError):
                parsed = [part.strip() for part in text.replace(";", ",").split(",")]
            if isinstance(parsed, (list, tuple, set)):
                values = list(parsed)
            else:
                values = [parsed]

        out: List[int] = []
        seen = set()
        for value in values:
            parsed_int = safe_to_int(value, 0)
            if parsed_int <= 0 or parsed_int in seen:
                continue
            seen.add(parsed_int)
            out.append(parsed_int)
        return out

    @staticmethod
    def merge_unique_int_list(
        base: List[Any],
        extra: List[Any],
        *,
        to_int_fn: Optional[Callable[[Any, int], int]] = None,
    ) -> List[int]:
        safe_to_int = to_int_fn or TransactionExportValueUtils.to_int
        out: List[int] = []
        seen = set()
        for raw_source in (base or [], extra or []):
            if isinstance(raw_source, (list, tuple, set)):
                values = list(raw_source)
            else:
                values = [raw_source]
            for source in values:
                parsed_int = safe_to_int(source, 0)
                if parsed_int <= 0 or parsed_int in seen:
                    continue
                seen.add(parsed_int)
                out.append(parsed_int)
        return out

    @staticmethod
    def count_unique_settlement_transactions(
        id_penjualan: List[Any],
        id_return: List[Any],
        *,
        merge_fn: Optional[Callable[[List[Any], List[Any]], List[int]]] = None,
    ) -> int:
        safe_merge = merge_fn or TransactionExportValueUtils.merge_unique_int_list
        merged_ids = safe_merge(id_penjualan, id_return)
        return int(len(merged_ids))
