#!/usr/bin/env python3
"""Export a BaoLife Python pickle save as JSON for the TypeScript backend.

The active Node container uses this only for one-time migration of legacy
`lifesim_savegames.pickle_data` rows. It deliberately avoids importing the
legacy app so old module paths do not have to exist in the runtime image.
"""

from __future__ import annotations

import base64
import io
import json
import pickle
import sys
from datetime import date, datetime
from decimal import Decimal
from uuid import UUID


class GenericPickleObject:
    def __init__(self, *args, **kwargs):
        if args:
            self.args = args
        self.__dict__.update(kwargs)

    def __setstate__(self, state):
        if isinstance(state, dict):
            self.__dict__.update(state)
        else:
            self.state = state


CLASS_CACHE = {}


def make_generic_class(module: str, name: str):
    key = (module, name)
    if key not in CLASS_CACHE:
        CLASS_CACHE[key] = type(name, (GenericPickleObject,), {"__module__": module})
    return CLASS_CACHE[key]


class SafeLegacyUnpickler(pickle.Unpickler):
    SAFE_MODULES = {
        "builtins",
        "datetime",
        "decimal",
        "uuid",
        "collections",
    }

    def find_class(self, module, name):
        if module in self.SAFE_MODULES:
            try:
                return super().find_class(module, name)
            except Exception:
                pass

        return make_generic_class(module, name)


def to_jsonable(value, seen):
    if value is None or isinstance(value, (str, int, float, bool)):
        return value

    if isinstance(value, Decimal):
        return float(value)

    if isinstance(value, UUID):
        return str(value)

    if isinstance(value, (datetime, date)):
        return value.isoformat()

    if isinstance(value, bytes):
        return {"__bytes_base64__": base64.b64encode(value).decode("ascii")}

    if isinstance(value, (list, tuple, set, frozenset)):
        return [to_jsonable(item, seen) for item in value]

    if isinstance(value, dict):
        return {str(key): to_jsonable(item, seen) for key, item in value.items()}

    object_id = id(value)
    if object_id in seen:
        return {"__ref__": seen[object_id]}

    ref_name = f"{value.__class__.__module__}.{value.__class__.__name__}:{len(seen)}"
    seen[object_id] = ref_name

    if hasattr(value, "__dict__"):
        data = {
            "__class__": f"{value.__class__.__module__}.{value.__class__.__name__}",
        }
        data.update({
            key: to_jsonable(item, seen)
            for key, item in vars(value).items()
            if not key.startswith("_")
        })
        return data

    return str(value)


def main() -> int:
    raw = sys.stdin.buffer.read()
    player = SafeLegacyUnpickler(io.BytesIO(raw)).load()
    print(json.dumps(to_jsonable(player, {}), separators=(",", ":")))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
