from typing import Any

import pytest
from sqlalchemy import Column
from sqlmodel import Field

from daomodel import DAOModel, names_of, Unsearchable


class SimpleModel(DAOModel, table=True):
    pkA: int = Field(primary_key=True)

simple_instance = SimpleModel(pkA=23)


class ForeignKEYModel(DAOModel, table=True):
    pkB: int = Field(primary_key=True)
    prop: str
    fkA: int = Field(foreign_key='simple_model.pkA')


class ComplicatedModel(DAOModel, table=True):
    pk1: int = Field(primary_key=True)
    pk2: int = Field(primary_key=True)
    prop1: str
    prop2: str
    fk1: int = Field(foreign_key='simple_model.pkA')
    fk2: int = Field(foreign_key='foreign_key_model.pkB')

    @classmethod
    def get_searchable_properties(cls) -> set[Column|tuple[DAOModel, ..., Column]]:
        return {cls.pk1, cls.pk2, cls.prop1, cls.fk1, cls.fk2, ForeignKEYModel.prop, (ForeignKEYModel, SimpleModel.pkA)}

complicated_instance = ComplicatedModel(pk1=17, pk2=76, prop1='prop', prop2='erty', fk1=23, fk2=32)


def test_tablename():
    assert SimpleModel.__tablename__ == 'simple_model'
    assert ForeignKEYModel.__tablename__ == 'foreign_key_model'


def test_normalized_name():
    assert SimpleModel.normalized_name() == 'simple_model'
    assert ForeignKEYModel.normalized_name() == 'foreign_key_model'


def test_doc_name():
    assert SimpleModel.doc_name() == 'Simple Model'
    assert ForeignKEYModel.doc_name() == 'Foreign Key Model'


def test_get_pk_names__single_column():
    assert SimpleModel.get_pk_names() == ['pkA']


def test_get_pk_names__multi_column():
    assert ComplicatedModel.get_pk_names() == ['pk1', 'pk2']


def test_get_pk_values__single_column():
    assert simple_instance.get_pk_values() == (23,)


def test_get_pk_values__multi_column():
    assert complicated_instance.get_pk_values() == (17, 76)


def test_get_pk_dict__single_column():
    assert simple_instance.get_pk_dict() == {'pkA': 23}


def test_get_pk_dict__multi_column():
    assert complicated_instance.get_pk_dict() == {'pk1': 17, 'pk2': 76}


def test_get_fks__single_column():
    assert names_of(ForeignKEYModel.get_fks()) == ['pkA']


def test_get_fks__multi_column():
    assert set(names_of(ComplicatedModel.get_fks())) == {'pkA', 'pkB'}


def test_get_fk_properties__single_column():
    assert names_of(ForeignKEYModel.get_fk_properties()) == ['fkA']


def test_get_fk_properties__multi_column():
    assert set(names_of(ComplicatedModel.get_fk_properties())) == {'fk1', 'fk2'}


def test_get_properties__single_column():
    assert names_of(SimpleModel.get_properties()) == ['pkA']


def test_get_properties__multi_column():
    assert names_of(ComplicatedModel.get_properties()) == ['pk1', 'pk2', 'prop1', 'prop2', 'fk1', 'fk2']


def test_get_searchable_properties__single_column():
    assert names_of(SimpleModel.get_searchable_properties()) == ['pkA']


def test_get_searchable_properties__multi_column():
    assert set(names_of(ForeignKEYModel.get_searchable_properties())) == {'pkB', 'prop', 'fkA'}


@pytest.mark.parametrize('prop', (
    ComplicatedModel.pk1,
    ComplicatedModel.pk2,
    ComplicatedModel.prop1,
    ComplicatedModel.fk1,
    ComplicatedModel.fk2,
    'complicated_model.pk1',
    'complicated_model.pk2',
    'complicated_model.prop1',
    'complicated_model.fk1',
    'complicated_model.fk2',
    'pk1',
    'pk2',
    'prop1',
    'fk1',
    'fk2'
))
def test_find_searchable_column(prop: str|Any):
    foreign_tables = []
    assert ComplicatedModel.find_searchable_column(prop, foreign_tables)
    assert not foreign_tables


@pytest.mark.parametrize('prop', (
    ForeignKEYModel.prop,
    'foreign_key_model.prop'
))
def test_find_searchable_column__foreign(prop: str|Any):
    foreign_tables = []
    assert ComplicatedModel.find_searchable_column(prop, foreign_tables)
    assert [t.name for t in foreign_tables] == [ForeignKEYModel.normalized_name()]


@pytest.mark.parametrize('prop', (
    SimpleModel.pkA,
    'simple_model.pkA'
))
def test_find_searchable_column__nested_foreign(prop: str|Any):
    foreign_tables = []
    assert ComplicatedModel.find_searchable_column(prop, foreign_tables)
    assert [t.name for t in foreign_tables] == [ForeignKEYModel.normalized_name(), SimpleModel.normalized_name()]


def test_find_searchable_column__foreign_without_table():
    with pytest.raises(Unsearchable):
        assert ComplicatedModel.find_searchable_column('prop', [])


def test_pk_values_to_dict__single_column():
    assert SimpleModel.pk_values_to_dict((23,)) == {'pkA': 23}


def test_pk_values_to_dict__multi_column():
    assert ComplicatedModel.pk_values_to_dict((17, 76)) == {'pk1': 17, 'pk2': 76}


def test_copy_model():
    other = ComplicatedModel(pk1=12, pk2=34, prop1='different', prop2='values', fk1=1, fk2=2)
    other.copy_model(complicated_instance)
    assert other.pk1 == 12
    assert other.pk2 == 34
    assert other.prop1 == 'prop'
    assert other.prop2 == 'erty'
    assert other.fk1 == 23
    assert other.fk2 == 32
    assert complicated_instance.pk1 == 17
    assert complicated_instance.pk2 == 76
    assert complicated_instance.prop1 == 'prop'
    assert complicated_instance.prop2 == 'erty'
    assert complicated_instance.fk1 == 23
    assert complicated_instance.fk2 == 32


def test_copy_values():
    other = ComplicatedModel(pk1=12, pk2=34, prop1='different', prop2='values', fk1=1, fk2=2)
    other.copy_values(pk1=0, prop1='new', other='extra')
    assert other.model_dump() == {
        'pk1': 12,
        'pk2': 34,
        'prop1': 'new',
        'prop2': 'values',
        'fk1': 1,
        'fk2': 2
    }


def test___eq___single_column():
    assert simple_instance == SimpleModel(pkA=23)
    assert simple_instance != SimpleModel(pkA=32)


def test___eq___multi_column():
    assert complicated_instance == ComplicatedModel(pk1=17, pk2=76, prop1='different', prop2='values', fk1=1, fk2=2)
    assert complicated_instance != ComplicatedModel(pk1=17, pk2=89, prop1='prop', prop2='erty', fk1=23, fk2=32)
    assert complicated_instance != ComplicatedModel(pk1=76, pk2=17, prop1='prop', prop2='erty', fk1=23, fk2=32)


def test___hash___single_column():
    assert hash(simple_instance) == hash(SimpleModel(pkA=23))
    assert hash(simple_instance) != hash(SimpleModel(pkA=32))


def test___hash___multi_column():
    assert hash(complicated_instance) == hash(ComplicatedModel(pk1=17, pk2=76, prop1='different', prop2='values', fk1=1, fk2=2))
    assert hash(complicated_instance) != hash(ComplicatedModel(pk1=17, pk2=89, prop1='prop', prop2='erty', fk1=23, fk2=32))
    assert hash(complicated_instance) != hash(ComplicatedModel(pk1=76, pk2=17, prop1='prop', prop2='erty', fk1=23, fk2=32))


def test___str___single_column():
    assert str(simple_instance) == '23'


def test___str___multi_column():
    assert str(complicated_instance) == '(17, 76)'
