import time
import uuid
from datetime import datetime
from typing import Optional

import pytest

from src.typedal import TypeDAL, TypedTable
from src.typedal.fields import StringField, TypedField, UUIDField
from src.typedal.mixins import Mixin, SlugMixin, TimestampsMixin


class AllMixins(TypedTable, SlugMixin, TimestampsMixin, slug_field="name"):
    name: str


class TableWithMixins(TypedTable, SlugMixin, slug_field="name", slug_suffix_length=1):
    name: str
    number: Optional[int]


with pytest.warns(DeprecationWarning):

    class TableWithMixinsWarns(TypedTable, SlugMixin, slug_field="name", slug_suffix=1):
        name: str
        number: Optional[int]


class TableWithTimestamps(TypedTable, TimestampsMixin):
    unrelated: str


def test_invalid_slug_initialization():
    with pytest.raises(ValueError):

        class WithoutSlugField(TypedTable, SlugMixin):  # no slug_field=...
            ...


@pytest.fixture
def db():
    _db = TypeDAL("sqlite:memory")

    _db.define(AllMixins)
    _db.define(TableWithMixins)
    _db.define(TableWithTimestamps)
    yield _db


def test_order(db):
    assert TableWithMixins.fields == [
        "id",  # id should always be the first (pydal itself already does this);
        "name",  # table own fields should come afterwards,
        "number",  # in the order they are defined;
        "slug",  # mixins should come last.
    ]


def test_slug(db):
    row = TableWithMixins.insert(name="Two Words")

    assert row.name == "Two Words"
    assert row.slug.startswith("two-words")

    assert TableWithMixins.from_slug(row.slug)
    assert TableWithMixins.from_slug("missing") is None

    assert TableWithMixins.from_slug_or_fail(row.slug)

    with pytest.raises(ValueError):
        TableWithMixins.from_slug_or_fail("missing")


def test_timestamps(db):
    row = TableWithTimestamps.insert(unrelated="Hi")
    db.commit()
    assert row.updated_at == row.created_at

    time.sleep(1)  # make sure datetimes are not equal

    row.update_record(unrelated="Bye")

    updated_row = TableWithTimestamps(id=row.id)

    assert updated_row.updated_at > updated_row.created_at


def test_reusing(db):
    assert str(AllMixins.created_at) == "all_mixins.created_at"
    assert str(AllMixins.slug) == "all_mixins.slug"
    assert str(AllMixins.name) == "all_mixins.name"

    assert str(TableWithMixins.slug) == "table_with_mixins.slug"
    assert str(TableWithMixins.name) == "table_with_mixins.name"

    assert str(TableWithTimestamps.created_at) == "table_with_timestamps.created_at"
    assert str(TableWithTimestamps.unrelated) == "table_with_timestamps.unrelated"


def test_combining_mixins():
    class FirstMixin(Mixin):
        def __init_subclass__(cls, first: str, **kw):
            super().__init_subclass__(**kw)

            cls.__settings__["first"] = first

        @classmethod
        def one(cls):
            return cls.__settings__["first"] == "first" and cls.__settings__["second"] == "second"

    class SecondMixin(Mixin):
        def __init_subclass__(cls, second: str, **kw):
            super().__init_subclass__(**kw)

            cls.__settings__["second"] = second

        @classmethod
        def two(cls):
            return cls.__settings__["first"] == "first" and cls.__settings__["second"] == "second"

    class Combined(TypedTable, FirstMixin, SecondMixin, first="first", second="second"): ...

    assert Combined.one()
    assert Combined.two()

    class CombinedDifferentOrder(TypedTable, SecondMixin, FirstMixin, first="first", second="second"): ...

    assert CombinedDifferentOrder.one()
    assert CombinedDifferentOrder.two()
