# Generated from example#ShipmentRepository by sql-service-codegen. Do not edit by hand.
from __future__ import annotations

import json
import sqlite3
from datetime import datetime, timezone
from typing import cast, override

import aiosqlite
from generated.example.models.shipment_repository_models import (
    DeliveryState,
    DeliveryStateDelivered,
    DeliveryStatePending,
    PostalAddress,
    Shipment,
)
from generated.example.shipment_repository_protocol import ShipmentRepositoryServiceProtocol
from generated.example.sqlite.sqlite_transaction_run import run


class ShipmentRepositoryAiosqliteService(ShipmentRepositoryServiceProtocol[aiosqlite.Connection]):
    def __init__(self, connection: aiosqlite.Connection) -> None:
        super().__init__()
        self._connection = connection

    @override
    async def create_shipment(
        self,
        label: str,
        destination: PostalAddress,
        state: DeliveryState,
        *,
        transaction: aiosqlite.Connection | None = None,
    ) -> str:
        async def execute(conn: aiosqlite.Connection) -> str:
            cursor = await conn.execute(
                """INSERT INTO shipments (label, destination, state) VALUES (?, ?, ?) RETURNING id;""",
                (label, _json_bind_PostalAddress(destination), _json_bind_DeliveryState(state)),
            )
            row = await cursor.fetchone()
            if row is None:
                raise RuntimeError("INSERT RETURNING produced no row")
            return _read_str(row, 0)

        return await run(self._connection, transaction, execute)

    @override
    async def get_shipment(
        self,
        id: str,
        *,
        transaction: aiosqlite.Connection | None = None,
    ) -> Shipment | None:
        async def execute(conn: aiosqlite.Connection) -> Shipment | None:
            cursor = await conn.execute(
                """SELECT shipments.id, shipments.label, shipments.destination, shipments.state, shipments.created_at
FROM shipments
WHERE id = ?;""",
                (id,),
            )
            row = await cursor.fetchone()
            if row is None:
                return None
            named_row = _as_sqlite_named_row(cursor, row)
            return Shipment(
                id=_read_str_col(named_row, "id"),
                label=_read_str_col(named_row, "label"),
                destination=_read_PostalAddress_col(named_row, "destination"),
                state=_read_DeliveryState_col(named_row, "state"),
                created_at=_read_datetime_col(named_row, "created_at"),
            )

        return await run(self._connection, transaction, execute)

    @override
    async def update_shipment(
        self,
        label: str,
        destination: PostalAddress,
        state: DeliveryState,
        id: str,
        *,
        transaction: aiosqlite.Connection | None = None,
    ) -> bool:
        async def execute(conn: aiosqlite.Connection) -> bool:
            cursor = await conn.execute(
                """UPDATE shipments
SET label = ?, destination = ?, state = ?
WHERE id = ?;""",
                (label, _json_bind_PostalAddress(destination), _json_bind_DeliveryState(state), id),
            )
            return cursor.rowcount > 0

        return await run(self._connection, transaction, execute)

    @override
    async def delete_shipment(
        self,
        id: str,
        *,
        transaction: aiosqlite.Connection | None = None,
    ) -> bool:
        async def execute(conn: aiosqlite.Connection) -> bool:
            cursor = await conn.execute(
                """DELETE FROM shipments WHERE id = ? RETURNING id;""",
                (id,),
            )
            row = await cursor.fetchone()
            return row is not None

        return await run(self._connection, transaction, execute)


def _map_json_timestamp(value: object) -> datetime:
    if isinstance(value, datetime):
        return value
    return datetime.fromisoformat(str(value))


def _dump_json_timestamp(value: datetime) -> str:
    return value.isoformat()


def _map_to_PostalAddress(data: dict[str, object]) -> PostalAddress:
    return PostalAddress(
        street=cast(str, data["street"]),
        city=cast(str, data["city"]),
    )


def _dump_PostalAddress(value: PostalAddress) -> dict[str, object]:
    return {
        "street": value.street,
        "city": value.city,
    }


def _map_to_DeliveryState(data: dict[str, object]) -> DeliveryState:
    present = [key for key in ("pending", "delivered") if key in data]
    if len(present) != 1:
        raise ValueError(f"unknown DeliveryState discriminator: {sorted(data.keys())}")
    if "pending" in data:
        return DeliveryStatePending(
            pending=cast(str, data["pending"]),
        )
    if "delivered" in data:
        return DeliveryStateDelivered(
            delivered=_map_json_timestamp(data["delivered"]),
        )
    raise ValueError(f"unknown DeliveryState discriminator: {sorted(data.keys())}")


def _dump_DeliveryState(value: DeliveryState) -> dict[str, object]:
    if isinstance(value, DeliveryStatePending):
        return {"pending": value.pending}
    if isinstance(value, DeliveryStateDelivered):
        return {"delivered": _dump_json_timestamp(value.delivered)}
    raise TypeError(f"unsupported DeliveryState variant: {type(value)!r}")


def _as_sqlite_named_row(
    cursor: sqlite3.Cursor | aiosqlite.Cursor,
    row: tuple[object, ...] | sqlite3.Row,
) -> dict[str, object]:
    if type(row) is not tuple:
        named = cast(sqlite3.Row, row)
        return {key: cast(object, named[key]) for key in named}
    description = cast(tuple[tuple[object, ...], ...], cursor.description)
    return {cast(str, column[0]): row[index] for index, column in enumerate(description)}


def _read_str(row: tuple[object, ...] | sqlite3.Row, index: int) -> str:
    return cast(str, row[index])


def _json_bind_DeliveryState(value: DeliveryState) -> str:
    return json.dumps(_dump_DeliveryState(value))


def _read_DeliveryState(row: tuple[object, ...] | sqlite3.Row, index: int) -> DeliveryState:
    data = cast(dict[str, object], json.loads(_read_str(row, index)))
    return _map_to_DeliveryState(data)


def _json_bind_PostalAddress(value: PostalAddress) -> str:
    return json.dumps(_dump_PostalAddress(value))


def _read_PostalAddress(row: tuple[object, ...] | sqlite3.Row, index: int) -> PostalAddress:
    data = cast(dict[str, object], json.loads(_read_str(row, index)))
    return _map_to_PostalAddress(data)


def _read_datetime_col(row: dict[str, object], column: str) -> datetime:
    value = cast(str, row[column])
    if value.endswith("Z"):
        normalized = value[:-1] + "+00:00"
    elif " " in value and "T" not in value:
        normalized = value.replace(" ", "T", 1) + "+00:00"
    else:
        normalized = value
    parsed = datetime.fromisoformat(normalized)
    if parsed.tzinfo is None:
        return parsed.replace(tzinfo=timezone.utc)
    return parsed.astimezone(timezone.utc)


def _read_str_col(row: dict[str, object], column: str) -> str:
    return cast(str, row[column])


def _read_DeliveryState_col(row: dict[str, object], column: str) -> DeliveryState:
    data = cast(dict[str, object], json.loads(_read_str_col(row, column)))
    return _map_to_DeliveryState(data)


def _read_PostalAddress_col(row: dict[str, object], column: str) -> PostalAddress:
    data = cast(dict[str, object], json.loads(_read_str_col(row, column)))
    return _map_to_PostalAddress(data)
