from .engine import Engine
from ..api.create import create
from ..api.write import write
from ..api.read import read
from ..api.delete import delete
from ..api.update import update
from ..api.serial import encode, decode
from ..api.namespace import Namespace


class LxDB:
    def __init__(self, path: str):
        self._engine = Engine(path)

    # ------------------------------------------------------------------
    # Raw CRUD
    # ------------------------------------------------------------------

    def create(self, key, value):
        create(self._engine, key, value)

    def write(self, key, value):
        write(self._engine, key, value)

    def read(self, key):
        return read(self._engine, key)

    def delete(self, key) -> bool:
        return delete(self._engine, key)

    def update(self, key, value):
        update(self._engine, key, value)

    # ------------------------------------------------------------------
    # Typed API
    # ------------------------------------------------------------------

    def set(self, key, value):
        if isinstance(key, str):
            key = key.encode()
        encoded = encode(value)
        if self._engine.has(key):
            self._engine.tombstone(key)
        self._engine.append(key, encoded)

    def get(self, key):
        if isinstance(key, str):
            key = key.encode()
        raw = self._engine.read(key)
        if raw is None:
            return None
        return decode(raw)

    # ------------------------------------------------------------------
    # Batch API
    # ------------------------------------------------------------------

    def batch_set(self, items: dict):
        encoded_pairs = []
        to_tombstone  = []
        for k, v in items.items():
            if isinstance(k, str):
                k = k.encode()
            if self._engine.has(k):
                to_tombstone.append(k)
            encoded_pairs.append((k, encode(v)))
        self._engine.tombstone_many(to_tombstone)
        self._engine.append_many(encoded_pairs)

    def batch_create(self, items: dict):
        pairs = []
        for k, v in items.items():
            if isinstance(k, str):
                k = k.encode()
            if self._engine.has(k):
                raise KeyError(f"Key already exists: {k!r}")
            pairs.append((k, encode(v)))
        self._engine.append_many(pairs)

    def batch_delete(self, keys: list) -> int:
        bkeys = [k.encode() if isinstance(k, str) else k for k in keys]
        return self._engine.tombstone_many(bkeys)

    # ------------------------------------------------------------------
    # Iteration
    # ------------------------------------------------------------------

    def keys(self) -> list:
        return list(self._engine._index.keys())

    def items(self) -> list:
        result = []
        for k in self._engine._index:
            result.append((k, self.get(k)))
        return result

    def values(self) -> list:
        return [self.get(k) for k in self._engine._index]

    def exists(self, key) -> bool:
        if isinstance(key, str):
            key = key.encode()
        return self._engine.has(key)

    # ------------------------------------------------------------------
    # Namespaces
    # ------------------------------------------------------------------

    def ns(self, name: str) -> Namespace:
        return Namespace(self, name)

    # ------------------------------------------------------------------
    # Maintenance
    # ------------------------------------------------------------------

    def compact(self):
        self._engine.compact()

    def flush(self):
        self._engine.flush()

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    def close(self):
        self._engine.close()

    def __enter__(self):
        return self

    def __exit__(self, *_):
        self.close()