
from dataclasses import dataclass
from typing import Any, Dict


@dataclass(frozen=True)
class DbUpdate():
    column_name: str
    """
    The name of the column this update applies to.
    """

    value: Any = None
    """
    The value the column should be updated to in the DB column.
    """


class ColumnMapper():
    """
    The base column mapper contains data and functions for mapping class attributes into columns in
    the database. Thi class can be derived from for special columns (e.g. body and signature).
    NOTE: Most attributes are stored as jsonb in the body column and don't need a column mapper. Only
    class attributes annotated with DbColumn will be mapped to their own columns.
    """
    @property
    def name(self) -> str:
        """
        The name of the column. This MUST match the attribute name and must be lowercase (e.g. snake_case)
        """
        return self._name

    @property
    def column_definition(self) -> str:
        """
        A SQL string that defines the column.
        """
        return self._column_definition

    @property
    def index(self) -> bool:
        """
        Indicates if the column is an index
        """
        return self._index

    @property
    def auto_generated(self) -> bool:
        """
        Indicates if the DB generates the value. If True, the column is excluded on inserts/updates.
        """
        return self._auto_generated

    @property
    def primary_key(self) -> bool:
        """
        This column is the primary key if True.
        """
        return self._primary_key

    def __init__(self, name: str, column_definition: str, index: bool = False, auto_generated: bool = False, primary_key: bool = False):
        self._name = name
        self._column_definition = column_definition
        self._index = index
        self._auto_generated = auto_generated
        self._primary_key = primary_key

    def db_row_to_object[T](self, from_db_row: Dict[str, Any], to_object_dict: Dict[str, Any]):
        """
        Takes the row received from the db and adds a key and value to the object_dict that correspond
        to this column's value from the row.

        :param from_db_row: A row retrieved from the DB containing a value for this column.
        :param to_object_dict: A dictionary that will be used to construct an object using pydantic's model_validate function
        """
        to_object_dict[self._name] = from_db_row[self.name]

    def object_to_db_update(self, obj_dict: Dict[str, Any]) -> DbUpdate | None:
        """
        Takes a dictionary representing an object, removes the key from it (if appropriate) and returns
        a SetCommand for the object. the value for this column. This function should
        modify the obj_dict if required. For most columns, this will delete the column's key from the obj_dict

        :param obj_dict: A dictionary representing the object's attributes. This comes from pydantic's model_dump function and
            will be used to serialize the object into the body column (jsonb). Most column values should be removed from this prior
            to serializing to jsonb.
        :rtype: The value from this object's dictionary that should be stored in this column in the DB
        """
        column_value = obj_dict[self.name]
        del obj_dict[self.name]
        if self.auto_generated:
            return None
        else:
            return DbUpdate(
                self.name,
                value=column_value,
            )
