#exonware/xwsystem/src/exonware/xwsystem/io/serialization/formats/database/sqlite3.py
"""
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.9.0.23
Generation Date: November 2, 2025
SQLite3 serialization - Embedded database storage.
Following I→A pattern:
- I: ISerialization (interface)
- A: ASerialization (abstract base)
- Concrete: Sqlite3Serializer
"""

import sqlite3
from typing import Any
from pathlib import Path
from ...base import ASerialization
from ...flyweight import get_serializer
from ...formats.text.json import JsonSerializer
# Reuse xwsystem JSON (no stdlib json)
_json = get_serializer(JsonSerializer)
from ....contracts import EncodeOptions, DecodeOptions
from ....defs import CodecCapability
from ....errors import SerializationError


class Sqlite3Serializer(ASerialization):
    """SQLite3 serializer - follows the I→A pattern."""
    @property

    def codec_id(self) -> str:
        return "sqlite3"
    @property

    def media_types(self) -> list[str]:
        return ["application/x-sqlite3", "application/vnd.sqlite3"]
    @property

    def file_extensions(self) -> list[str]:
        return [".db", ".sqlite", ".sqlite3"]
    @property

    def format_name(self) -> str:
        return "SQLite3"
    @property

    def mime_type(self) -> str:
        return "application/x-sqlite3"
    @property

    def is_binary_format(self) -> bool:
        return True
    @property

    def supports_streaming(self) -> bool:
        return False
    @property

    def capabilities(self) -> CodecCapability:
        return CodecCapability.BIDIRECTIONAL
    @property

    def aliases(self) -> list[str]:
        return ["sqlite3", "sqlite", "db"]

    def encode(self, value: Any, *, options: EncodeOptions | None = None) -> bytes | str:
        """SQLite3 encode requires file path - use save_file() instead."""
        raise NotImplementedError("SQLite3 requires file-based operations - use save_file()")

    def decode(self, repr: bytes | str, *, options: DecodeOptions | None = None) -> Any:
        """SQLite3 decode requires file path - use load_file() instead."""
        raise NotImplementedError("SQLite3 requires file-based operations - use load_file()")
    # ---------------------------------------------------------------------
    # FILE-BASED OPERATIONS (override ASerialization defaults)
    # ---------------------------------------------------------------------

    def save_file(self, data: Any, file_path: str | Path, **options: Any) -> None:
        """
        Save Python data into a SQLite3 database file.
        Root cause: The generic ``ASerialization.save_file`` implementation
        expects an in-memory ``encode`` operation, but SQLite3 is inherently
        file-backed and our ``encode``/``decode`` are intentionally
        unimplemented. Calling the base implementation raised a
        ``NotImplementedError`` wrapped in ``SerializationError``.
        Solution: Override ``save_file`` / ``load_file`` to perform
        file-based operations directly using ``sqlite3`` while still
        presenting a simple dict-like API to callers and tests.
        """
        path = Path(file_path)
        path.parent.mkdir(parents=True, exist_ok=True)
        # For now we implement a minimal schema:
        # - Single table ``data``
        # - Single row with id=1 storing JSON representation of ``data``
        try:
            conn = sqlite3.connect(path)
            try:
                cur = conn.cursor()
                cur.execute(
                    "CREATE TABLE IF NOT EXISTS data ("
                    "id INTEGER PRIMARY KEY, "
                    "json TEXT NOT NULL"
                    ")"
                )
                raw = _json.dumps(data)
                json_str = raw.decode("utf-8") if isinstance(raw, bytes) else raw
                # Ensure id=1 holds the latest payload
                cur.execute("DELETE FROM data WHERE id = 1")
                cur.execute("INSERT INTO data (id, json) VALUES (1, ?)", (json_str,))
                conn.commit()
            finally:
                conn.close()
        except sqlite3.Error as e:
            raise SerializationError(
                f"Failed to save SQLite3 file '{path}': {e}",
                format_name=self.format_name,
                original_error=e,
            ) from e

    def load_file(self, file_path: str | Path, **options: Any) -> Any:
        """
        Load Python data from a SQLite3 database file.
        Reads row with ``id = 1`` from the ``data`` table and returns the
        JSON-decoded payload. This mirrors the simple dict-based contract
        used by the tests and higher-level APIs.
        """
        path = Path(file_path)
        if not path.exists():
            raise FileNotFoundError(f"SQLite3 file not found: {path}")
        try:
            conn = sqlite3.connect(path)
            try:
                cur = conn.cursor()
                cur.execute(
                    "SELECT json FROM data WHERE id = 1"
                )
                row = cur.fetchone()
                if row is None or row[0] is None:
                    # No payload stored yet – mirror behaviour of other
                    # serializers by returning None.
                    return None
                return _json.loads(row[0])
            finally:
                conn.close()
        except sqlite3.Error as e:
            raise SerializationError(
                f"Failed to load SQLite3 file '{path}': {e}",
                format_name=self.format_name,
                original_error=e,
            ) from e
