"""JSON custom serializers and deserializers."""

import datetime
import json
import logging
import os

try:
    from datetime import UTC
except ImportError:
    from datetime import timezone

    UTC = timezone.utc

from sbws.util.filelock import FileLock

from .timestamps import DateTimeIntSeq, DateTimeSeq

log = logging.getLogger(__name__)


class CustomEncoder(json.JSONEncoder):
    """JSONEncoder that serializes datetime to ISO 8601 string."""

    def default(self, obj):
        if isinstance(obj, DateTimeSeq) or isinstance(obj, DateTimeIntSeq):
            return [self.default(i) for i in obj.list()]
        if isinstance(obj, datetime.datetime):
            return obj.replace(microsecond=0).isoformat()
        else:
            return super().default(obj)


class CustomDecoder(json.JSONDecoder):
    """JSONDecoder that deserializes ISO 8601 string to datetime."""

    def decode(self, s, **kwargs):
        decoded = super().decode(s, **kwargs)
        return self.process(decoded)

    def process(self, obj):
        if isinstance(obj, list) and obj:
            return [self.process(item) for item in obj]
        if isinstance(obj, dict):
            return {key: self.process(value) for key, value in obj.items()}
        if isinstance(obj, str):
            try:
                return datetime.datetime.strptime(obj, "%Y-%m-%dT%H:%M:%S")
            except ValueError:
                try:
                    datetime.datetime.strptime(
                        obj, "%Y-%m-%dT%H:%M:%S.%f"
                    ).replace(microsecond=0)
                except ValueError:
                    pass
            except TypeError:
                pass
        return obj


def atomic_dump(jsond: dict, jsonpath: str):
    # Ensure dump is atomic writing it to a temporary file and then using
    # 'rename`.
    # Can't use tempfile.NamedTemporaryFile cause if /tmp is in a different
    # filesystem, rename isn't possible.
    tmppath: str = f"{jsonpath}.{datetime.datetime.now(UTC).timestamp()}.tmp"
    with open(tmppath, "w") as fd:
        try:
            json.dump(jsond, fd, indent=4, cls=CustomEncoder)
        except Exception as e:  # Catch all errors
            log.warning(
                "json: %s, path: %s, ignoring error: %s", jsond, tmppath, e
            )
            return
    with FileLock(jsonpath):
        os.rename(tmppath, jsonpath)


def load_ignore_errors(jsonpath: str) -> dict:
    if not os.path.exists(jsonpath):
        return {}
    with FileLock(jsonpath):
        with open(jsonpath, "r") as fd:
            try:
                return json.load(fd, cls=CustomDecoder)
            except json.decoder.JSONDecodeError as e:
                log.warning("path: %s, ignoring error: %s", jsonpath, e)
                return {}
