import decimal
from typing import TypeVar, Callable, Generic, Type, NewType, runtime_checkable, Protocol
import abc
import warnings
import datetime
import json


JSON = str | int | list["JSON"] | dict[str, "JSON"]

Q = TypeVar("Q")

COLUMN_TYPES = {}
SQLITE_TYPES = {}


class ColumnType(abc.ABC, Generic[Q]):
    sqlite_type: str
    handles: Type[Q]

    @abc.abstractmethod
    def to_sqlite(self, value: Q) -> bytes:
        pass

    @abc.abstractmethod
    def to_python(self, value: bytes) -> Q:
        pass

    @staticmethod
    def from_callables(python_type: Type[Q], to_sqlite_converter: Callable[[Q], bytes], sqlite_type: str, to_python_converter: Callable[[bytes], Q]) -> "ColumnType":
        return FunctionColumnType(python_type, to_sqlite_converter, sqlite_type, to_python_converter)


class FunctionColumnType(ColumnType[Q], Generic[Q]):

    def __init__(self, python_type: Type[Q], to_sqlite_converter: Callable[[Q], bytes], sqlite_type: str, to_python_converter: Callable[[bytes], Q]):
        self.handles = python_type
        self.to_sqlite_converter = to_sqlite_converter
        self.sqlite_type = sqlite_type
        self.to_python_converter = to_python_converter

    def to_sqlite(self, value: Q) -> bytes:
        return self.to_sqlite_converter(value)

    def to_python(self, value: bytes) -> Q:
        return self.to_python_converter(value)


class DefaultType(ColumnType):

    def __init__(self, python_type, sqlite_type):
        self.handles = python_type
        self.sqlite_type = sqlite_type

    def to_sqlite(self, value):
        return value

    def to_python(self, value):
        return value


def register_column_type(type: ColumnType) -> None:
    if type.handles in COLUMN_TYPES:
        warnings.warn(f"Overriding handler for python type {type.handles}")
    COLUMN_TYPES[type.handles] = type

    if type.sqlite_type in SQLITE_TYPES:
        warnings.warn(f"Overriding handler for sqlite type {type.sqlite_type}")
    SQLITE_TYPES[type.sqlite_type] = type


register_column_type(DefaultType(str, "TEXT"))
register_column_type(DefaultType(bytes, "BLOB"))
register_column_type(DefaultType(int, "INTEGER"))
register_column_type(DefaultType(float, "REAL"))


def _decimal_to_sqlite(d: decimal.Decimal) -> bytes:
    return str(d).encode("utf8")


def _sqlite_to_decimal(s: bytes) -> decimal.Decimal:
    return decimal.Decimal(s.decode("utf8"))


register_column_type(ColumnType.from_callables(decimal.Decimal, _decimal_to_sqlite, "DECIMAL", _sqlite_to_decimal))


def _bool_to_sqlite(b: bool) -> bytes:
    return b"1" if b else b"0"

def _sqlite_to_bool(s: bytes) -> bool:
    return s == b"1"


register_column_type(ColumnType.from_callables(bool, _bool_to_sqlite, "BOOL", _sqlite_to_bool))


def _date_to_sqlite(val: datetime.date) -> bytes:
    return val.isoformat().encode("utf8")


def _sqlite_to_date(val: bytes) -> datetime.date:
    return datetime.date.fromisoformat(val.decode())


register_column_type(ColumnType.from_callables(datetime.date, _date_to_sqlite, "DATE", _sqlite_to_date))


def _datetime_to_sqlite(val: datetime.datetime) -> bytes:
    return val.isoformat().encode("utf8")


def _sqlite_to_datetime(val: bytes) -> datetime.datetime:
    return datetime.datetime.fromisoformat(val.decode())


register_column_type(ColumnType.from_callables(datetime.datetime, _datetime_to_sqlite, "DATETIME", _sqlite_to_datetime))

def _json_to_sqlite(val: JSON) -> bytes:
    return json.dumps(val).encode("utf8")

def _sqlite_to_json(val: bytes) -> JSON:
    return json.loads(val.decode("utf8"))

JSONType = ColumnType.from_callables(JSON, _json_to_sqlite, "JSON", _sqlite_to_json)
JSONDictType = ColumnType.from_callables(dict, _json_to_sqlite, "JSON", _sqlite_to_json)
JSONListType = ColumnType.from_callables(list, _json_to_sqlite, "JSON", _sqlite_to_json)

COLUMN_TYPES[JSON] = JSONType
COLUMN_TYPES[dict] = JSONDictType
COLUMN_TYPES[list] = JSONListType
SQLITE_TYPES["JSON"] = JSONType
