from collections.abc import Mapping, Sequence
from typing import assert_type
from uuid import UUID

from sqlmodel import Field as SQLField

from fastsprout.actions import DataS, DataSable, Page, Service, WriteOperation
from fastsprout.actions.service.datas.implementation import commands, queries
from fastsprout.actions.service.datas.schemas import configure_schemas
from fastsprout.actions.service.datas.types import DataSOperation, SchemaSetting
from fastsprout.core import BaseSchema, Depends, Field, IdentificatorType, InternalField, ReadDTO, ReadField, Schema, Undefined, WriteDTO, dataclass
from fastsprout.core.depends import DependsClass
from fastsprout.core.types import AnyIterable
from fastsprout.data import sql
from fastsprout.data import DataR
from fastsprout.data.capabilities.query import BaseQuery
from fastsprout.data.entity import Entitieable
from fastsprout.data.types import Valuable
from fastsprout.data.streams.protocols import AsyncCommonStreamable


class Hero(sql.SQLEntity[UUID]):
    id: ReadField[UUID] = SQLField(primary_key=True)
    name: Field[str]
    score: Field[int]
    secret: InternalField[str]


class Other(sql.SQLEntity[UUID]):
    id: ReadField[UUID] = SQLField(primary_key=True)
    name: Field[str]


class HeroRead(ReadDTO, Hero): ...


class HeroCreate(BaseSchema):
    name: str


class HeroPatch(BaseSchema):
    score: int


async def hero_validation(
    entity: Hero, /, *, operation: WriteOperation, datar: DataR
) -> Hero:
    assert_type(entity.score, int)
    return entity


def check_hero(entity: Hero, /, *, datar: DataR) -> None:
    assert_type(entity.name, str)


async def check_other(entity: Other, /, *, datar: DataR) -> None:
    assert_type(entity.name, str)


def actor_name() -> str:
    return "Batman"


_ACTOR = Depends(actor_name)


async def check_actor(
    entity: Hero, /, *, datar: DataR, actor: str = _ACTOR
) -> None:
    assert_type(entity.name, str)
    assert_type(actor, str)


async def check_delete(key: IdentificatorType, /, *, datar: DataR) -> None:
    assert_type(key, IdentificatorType)


async def checks[Statement](
    identifier: UUID,
    query: BaseQuery[Hero, Statement],
    other_query: BaseQuery[Other, Statement],
) -> None:
    default = DataS(Hero)
    contract: DataSable[Hero] = default
    assert_type(await contract.get(identifier), Hero | None)
    assert_type(await default.get(identifier), Hero | None)
    assert_type(await default.exists(identifier), bool)
    assert_type(await default.list(), Page[Hero])
    assert_type(await default.count(), int)
    automatic = DataS(Hero, read=Undefined, create=Undefined)
    assert_type(await automatic.get(identifier), Hero | None)
    disabled = DataS(Hero, create=None)
    assert_type(await disabled.exists(identifier), bool)
    assert_type(await disabled.get(identifier), Hero | None)
    assert_type(disabled.create_schema, type[Hero])
    disabled_contract: DataSable[Hero, Hero, Hero, Hero, Hero, None] = disabled
    await disabled_contract.create(Hero(id=identifier, name="Batman", score=1, secret="x"))  # error: reportAttributeAccessIssue; mypy: misc
    await disabled.create(Hero(id=identifier, name="Batman", score=1, secret="x"))  # error: reportAttributeAccessIssue; mypy: misc
    validated = DataS(Hero, dependencies=[Depends(hero_validation)])
    assert_type(await validated.get(identifier), Hero | None)
    await default.exists(None)  # error: reportArgumentType; mypy: arg-type
    guarded = DataS(Hero, dependencies=[Depends(check_hero), Depends(check_actor), Depends(check_delete)])
    assert_type(guarded.dependencies, Sequence[DependsClass])
    assert_type(await guarded.get(identifier), Hero | None)
    DataS(Hero, dependencies=[check_hero])  # error: reportArgumentType; mypy: list-item
    custom = DataS(Hero, read=HeroRead, create=HeroCreate, patch=HeroPatch)
    assert_type(await custom.create(HeroCreate(name="Batman")), HeroRead)
    assert_type(await custom.patch(identifier, HeroPatch(score=1)), HeroRead)
    assert_type(await custom.list(query.where(lambda hero: hero.score >= 1)), Page[HeroRead])
    assert_type(await custom.count(query.where(lambda hero: hero.score >= 1)), int)
    assert_type(query, BaseQuery[Hero, Statement])
    assert_type(await custom.list(query), Page[HeroRead])
    await custom.create(HeroPatch(score=1))  # error: reportArgumentType; mypy: arg-type
    await custom.patch(identifier, HeroCreate(name="Batman"))  # error: reportArgumentType; mypy: arg-type
    query.where(HeroPatch(score=1))  # error: reportArgumentType; mypy: arg-type
    await default.list(other_query)  # error: reportArgumentType; mypy: arg-type


async def batch_checks(
    heroes: AnyIterable[Hero],
    others: AnyIterable[Other],
    creates: AnyIterable[HeroCreate],
    updates: AnyIterable[tuple[IdentificatorType, HeroPatch]],
    deletes: AnyIterable[IdentificatorType],
) -> None:
    default = DataS(Hero)
    created: AsyncCommonStreamable[Hero] = default.bulk_create(heroes)
    updated: AsyncCommonStreamable[Hero] = default.bulk_update(heroes)
    assert_type(await created.to_list(), list[Hero])
    assert_type(await updated.to_list(), list[Hero])
    assert_type(await default.bulk_create(heroes).to_list(), list[Hero])
    assert_type(await default.bulk_update(heroes).to_list(), list[Hero])
    assert_type(await default.bulk_delete(deletes), int)
    custom = DataS(Hero, read=HeroRead, create=HeroCreate, update=HeroPatch, patch=HeroPatch)
    assert_type(custom.bulk_create(creates), AsyncCommonStreamable[HeroRead])
    assert_type(custom.bulk_update(updates), AsyncCommonStreamable[HeroRead])
    assert_type(custom.bulk_patch(updates), AsyncCommonStreamable[HeroRead])
    assert_type(custom.bulk_upsert(heroes), AsyncCommonStreamable[HeroRead])
    assert_type(await custom.bulk_create(creates).to_list(), list[HeroRead])
    assert_type(await custom.bulk_update(updates).to_list(), list[HeroRead])
    assert_type(await custom.bulk_patch(updates).to_list(), list[HeroRead])
    await custom.bulk_create(heroes).to_list()
    await custom.bulk_create(others).to_list()  # error: reportArgumentType; mypy: arg-type
    await custom.bulk_patch(creates).to_list()  # error: reportArgumentType; mypy: arg-type
    datar = DataR(dependencies=[Depends(check_hero)])
    assert_type(datar.dependencies, Sequence[DependsClass])
    DataR(dependencies=[check_hero])  # error: reportArgumentType; mypy: list-item


@dataclass(kw_only=True)
class MemoryEntity(Entitieable[UUID, None]):
    id: ReadField[UUID]
    name: Field[str]
    secret: InternalField[str]


@dataclass
class MemoryInput(Schema):
    name: str


class MemoryRead(ReadDTO, MemoryEntity): ...


async def non_sql_checks(
    identifier: UUID,
) -> None:
    default = DataS(MemoryEntity)
    assert_type(await default.get(identifier), MemoryEntity | None)
    assert_type(await default.list(), Page[MemoryEntity])
    custom = DataS(MemoryEntity, read=MemoryRead, create=MemoryInput)
    assert_type(await custom.create(MemoryInput(name="Batman")), MemoryRead)
    assert_type(await custom.list(), Page[MemoryRead])
    await custom.create(HeroCreate(name="Batman"))  # error: reportArgumentType; mypy: arg-type


class HeroSummary(BaseSchema):
    label: str


class SelectedActions(Service):
    entity = Hero
    read_schema = HeroRead
    create_schema = HeroCreate
    chunk_size = 10

    create = commands.create
    bulk_create = commands.bulk_create
    find_first = queries.find_first


async def selection_checks[Statement](query: BaseQuery[Hero, Statement], other: BaseQuery[Other, Statement]) -> None:
    selected = SelectedActions()
    assert_type(await selected.create(HeroCreate(name="Batman")), HeroRead)
    assert_type(await selected.find_first(query), HeroRead | None)
    assert_type(selected.bulk_create([HeroCreate(name="Batman")]), AsyncCommonStreamable[HeroRead])
    await selected.create(HeroPatch(score=1))  # error: reportArgumentType; mypy: arg-type
    service = DataS(Hero, read=HeroRead)
    assert_type(await service.get(query), HeroRead | None)
    assert_type(await service.exists(query), bool)
    assert_type(await service.find_first(query), HeroRead | None)
    assert_type(await service.find_exactly_one(query), HeroRead)
    assert_type(await service.find_all(query), list[HeroRead])
    assert_type(service.stream(query), AsyncCommonStreamable[HeroRead])
    async for item in service.stream(query):
        assert_type(item, HeroRead)
    async for summary in service.map(query, lambda hero: HeroSummary(label=hero.name)):
        assert_type(summary, HeroSummary)
    async for hero, related in service.join(query, other, on=(Hero.id, Other.id)):
        assert_type(hero, HeroRead)
        assert_type(related, Other)

async def query_reads[Statement](
    service: DataS[Hero, HeroRead],
    contract: DataSable[Hero, HeroRead],
    query: BaseQuery[Hero, Statement],
    wrong: BaseQuery[MemoryEntity, Statement],
) -> None:
    assert_type(await service.find_first(query), HeroRead | None)
    assert_type(await service.find_exactly_one(query), HeroRead)
    assert_type(await service.find_all(query), list[HeroRead])
    assert_type(service.stream(query), AsyncCommonStreamable[HeroRead])
    assert_type(await contract.find_first(query), HeroRead | None)
    assert_type(await contract.find_exactly_one(query), HeroRead)
    assert_type(await contract.find_all(query), list[HeroRead])
    async for item in contract.stream(query):
        assert_type(item, HeroRead)
    await service.find_first(wrong)  # error: reportArgumentType; mypy: arg-type
    service.stream(wrong)  # error: reportCallIssue, reportArgumentType; mypy: arg-type

async def query_writes[Statement](
    service: DataS[Hero],
    contract: DataSable[Hero],
    query: BaseQuery[Hero, Statement],
    wrong: BaseQuery[MemoryEntity, Statement],
) -> None:
    assert_type(await service.update_by_query(query, Hero.score.set(20)), int)
    assert_type(await service.delete_by_query(query), int)
    assert_type(await contract.update_by_query(query, Hero.score.set(20)), int)
    assert_type(await contract.delete_by_query(query), int)
    assert_type(await service.update_by_query(Hero.score.gte(20), Hero.score.set(30)), int)
    assert_type(await service.delete_by_query(Hero.score.gte(20)), int)
    assert_type(await contract.update_by_query(Hero.score.gte(20), Hero.score.set(30)), int)
    assert_type(await contract.delete_by_query(Hero.score.gte(20)), int)
    await service.update_by_query(Other.name.eq("Robin"), Hero.score.set(20))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    await service.delete_by_query(Other.name.eq("Robin"))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    await contract.update_by_query(Other.name.eq("Robin"), Hero.score.set(20))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    await contract.delete_by_query(Other.name.eq("Robin"))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    assert_type(await service.update_by_query(Hero.score.gte(20), Hero.score.lt(40), Hero.score.set(30)), int)
    assert_type(await service.delete_by_query(Hero.score.gte(20), Hero.score.lt(40)), int)
    assert_type(await contract.update_by_query(query.where(Hero.score.gte(20)), Hero.score.set(30)), int)
    assert_type(await contract.delete_by_query(query.where(Hero.score.gte(20))), int)
    await service.update_by_query(Hero.score.gte(20), Other.name.eq("Robin"), Hero.score.set(30))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    await service.delete_by_query(Hero.score.gte(20), Other.name.eq("Robin"))  # error: reportArgumentType; mypy: arg-type
    await contract.update_by_query(Hero.score.gte(20), Other.name.eq("Robin"), Hero.score.set(30))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    await contract.delete_by_query(Hero.score.gte(20), Other.name.eq("Robin"))  # error: reportArgumentType; mypy: arg-type
    await service.update_by_query(query, Hero.score.gte(20), Hero.score.set(30))  # error: reportCallIssue, reportArgumentType; mypy: call-overload
    await service.delete_by_query(query, Hero.score.gte(20))  # error: reportArgumentType; mypy: call-overload
    await contract.update_by_query(query, Hero.score.gte(20), Hero.score.set(30))  # error: reportCallIssue, reportArgumentType; mypy: call-overload
    await contract.delete_by_query(query, Hero.score.gte(20))  # error: reportArgumentType; mypy: call-overload
    await service.update_by_query(wrong, Hero.score.set(20))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    await service.delete_by_query(wrong)  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    await service.update_by_query(query, Hero.score.set("bad"))  # error: reportArgumentType; mypy: arg-type

def stream_predicate_types[Statement](
    service: DataS[Hero, HeroRead], contract: DataSable[Hero, HeroRead],
    query: BaseQuery[Hero, Statement],
) -> None:
    assert_type(service.stream(query), AsyncCommonStreamable[HeroRead])
    assert_type(service.stream(Hero.score.gte(20)), AsyncCommonStreamable[HeroRead])
    assert_type(service.stream(Hero.score.gte(20), Hero.name.eq("Robin")), AsyncCommonStreamable[HeroRead])
    assert_type(contract.stream(query), AsyncCommonStreamable[HeroRead])
    assert_type(contract.stream(Hero.score.gte(20)), AsyncCommonStreamable[HeroRead])
    assert_type(contract.stream(Hero.score.gte(20), Hero.name.eq("Robin")), AsyncCommonStreamable[HeroRead])
    service.stream(query, Hero.score.gte(20))  # error: reportArgumentType; mypy: call-overload
    contract.stream(query, Hero.score.gte(20))  # error: reportArgumentType; mypy: call-overload
    service.stream(Other.name.eq("Robin"))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    contract.stream(Other.name.eq("Robin"))  # error: reportCallIssue, reportArgumentType; mypy: arg-type
    service.stream(Hero.score.gte(20), Other.name.eq("Robin"))  # error: reportArgumentType; mypy: arg-type
    contract.stream(Hero.score.gte(20), Other.name.eq("Robin"))  # error: reportArgumentType; mypy: arg-type


async def upsert_types(
    service: DataS[Hero, HeroRead], contract: DataSable[Hero, HeroRead], entity: Hero,
) -> None:
    assert_type(await service.upsert(entity), HeroRead)
    assert_type(await contract.upsert(entity), HeroRead)
    async for row in service.bulk_upsert([entity]):
        assert_type(row, HeroRead)
    async for row in contract.bulk_upsert([entity]):
        assert_type(row, HeroRead)
    await service.upsert(HeroCreate(name="Batman"))  # error: reportArgumentType; mypy: arg-type


async def selection_types[Left, Right](
    service: DataS[Hero, HeroRead],
    contract: DataSable[Hero, HeroRead],
    query: BaseQuery[Hero, Left],
    other: BaseQuery[Other, Right],
) -> None:
    async for row in service.join(query, other, on=(Hero.id, Other.id)):
        assert_type(row, tuple[HeroRead, Other])
    async for row in contract.join(query, other, on=(Hero.id, Other.id)):
        assert_type(row, tuple[HeroRead, Other])
    async for summary in service.map(query, lambda row: HeroSummary(label=row.name)):
        assert_type(summary, HeroSummary)
    async for summary in contract.map(query, lambda row: HeroSummary(label=row.name)):
        assert_type(summary, HeroSummary)
    service.map(other, lambda row: HeroSummary(label=row.name))  # error: reportArgumentType; mypy: arg-type
    service.join(other, other, on=(Hero.id, Other.id))  # error: reportArgumentType; mypy: arg-type


async def summarize_async(hero: HeroRead) -> HeroSummary:
    return HeroSummary(label=hero.name.upper())


async def async_mapping_types[Statement](
    service: DataS[Hero, HeroRead],
    contract: DataSable[Hero, HeroRead],
    query: BaseQuery[Hero, Statement],
) -> None:
    async for summary in service.map(query, summarize_async):
        assert_type(summary, HeroSummary)
    async for summary in contract.map(query, summarize_async):
        assert_type(summary, HeroSummary)


class HeroWrite(WriteDTO, Hero): ...


async def explicit_dto_types(payload: HeroWrite) -> None:
    service = DataS(Hero, create=HeroWrite, read=HeroRead)
    assert_type(service.create_schema, type[HeroWrite])
    assert_type(service.read_schema, type[HeroRead])
    created = await service.create(payload)
    assert_type(created, HeroRead)
    assert_type(created.name, str)
    assert_type(created.id, UUID)
    _ = payload.id  # error: reportAttributeAccessIssue; mypy: call-overload
    _ = created.secret  # error: reportAttributeAccessIssue; mypy: call-overload

async def exact_match_conditions[Statement](
    service: DataS[Hero, HeroRead],
    contract: DataSable[Hero, HeroRead],
    query: BaseQuery[Hero, Statement],
) -> None:
    assert_type(await service.find_exactly_one(Hero.name.eq("Batman")), HeroRead)
    assert_type(await service.find_exactly_one(Hero.name.eq("Batman"), Hero.score.gte(10)), HeroRead)
    assert_type(await contract.find_exactly_one(Hero.name.eq("Batman")), HeroRead)
    assert_type(await contract.find_exactly_one(Hero.name.eq("Batman"), Hero.score.gte(10)), HeroRead)
    await service.find_exactly_one(Other.name.eq("Batman"))  # error: reportArgumentType; mypy: arg-type
    await contract.find_exactly_one(Other.name.eq("Batman"))  # error: reportArgumentType; mypy: arg-type
    await service.find_exactly_one(Hero.name.eq("Batman"), Other.name.eq("Batman"))  # error: reportArgumentType; mypy: arg-type
    await contract.find_exactly_one(Hero.name.eq("Batman"), Other.name.eq("Batman"))  # error: reportArgumentType; mypy: arg-type



def mixed_schema_configuration[
    E: Entitieable,
    R: Valuable | Entitieable,
    C: Schema | Entitieable,
    U: Schema | Entitieable,
    P: Schema | Entitieable,
](
    service: Service,
    entity: type[E],
    settings: Mapping[str, SchemaSetting[R | C | U | P]],
) -> None:
    assert_type(configure_schemas(service, entity, settings), frozenset[DataSOperation])
