import _typeshed
from collections import OrderedDict
from collections.abc import Iterator, Sequence
from functools import partial
from logging import Logger
from typing import Any, ClassVar, Generic, Literal, NoReturn, TypeAlias, TypeVar, overload
from typing_extensions import deprecated

from django.db.models import Field as DjangoField, Model, QuerySet
from django.utils.safestring import SafeString

from .declarative import DeclarativeMetaclass, ModelDeclarativeMetaclass
from .fields import Field
from .instance_loaders import BaseInstanceLoader
from .options import ResourceOptions
from .results import Error, Result, RowResult
from .widgets import ForeignKeyWidget, ManyToManyWidget, Widget

Dataset: TypeAlias = _typeshed.Incomplete  # tablib.Dataset
logger: Logger

def has_natural_foreign_key(model: Model) -> bool: ...

class Diff:
    left: list[str]
    right: list[str]
    new: bool
    def __init__(self, resource: Resource[_ModelT], instance: _ModelT, new: bool) -> None: ...
    def compare_with(self, resource: Resource[_ModelT], instance: _ModelT, dry_run: bool = False) -> None: ...
    def as_html(self) -> list[SafeString]: ...

_ModelT = TypeVar("_ModelT", bound=Model)

class Resource(Generic[_ModelT], metaclass=DeclarativeMetaclass):
    _meta: ResourceOptions[_ModelT]
    fields: OrderedDict[str, Field]
    create_instances: list[_ModelT]
    update_instances: list[_ModelT]
    delete_instances: list[_ModelT]
    def __init__(self, **kwargs: Any) -> None: ...
    @classmethod
    def get_result_class(self) -> type[Result]: ...
    @classmethod
    def get_row_result_class(self) -> type[RowResult]: ...
    @classmethod
    def get_error_result_class(self) -> type[Error]: ...
    @classmethod
    def get_diff_class(self) -> type[Diff]: ...
    @classmethod
    def get_db_connection_name(self) -> str: ...
    def get_use_transactions(self) -> bool: ...
    def get_chunk_size(self) -> int: ...
    @deprecated("The 'get_fields()' method is deprecated and will be removed in a future release.")
    def get_fields(self, **kwargs: Any) -> list[Field]: ...
    def get_field_name(self, field: Field) -> str: ...
    def init_instance(self, row: dict[str, Any] | None = None) -> _ModelT: ...
    def get_instance(self, instance_loader: BaseInstanceLoader, row: dict[str, Any]) -> _ModelT | None: ...
    def get_or_init_instance(self, instance_loader: BaseInstanceLoader, row: dict[str, Any]) -> tuple[_ModelT | None, bool]: ...
    def get_import_id_fields(self) -> Sequence[str]: ...
    def get_bulk_update_fields(self) -> list[str]: ...
    def bulk_create(
        self,
        using_transactions: bool,
        dry_run: bool,
        raise_errors: bool,
        batch_size: int | None = None,
        result: Result | None = None,
    ) -> None: ...
    def bulk_update(
        self,
        using_transactions: bool,
        dry_run: bool,
        raise_errors: bool,
        batch_size: int | None = None,
        result: Result | None = None,
    ) -> None: ...
    def bulk_delete(self, using_transactions: bool, dry_run: bool, raise_errors: bool, result: Result | None = None) -> None: ...
    def validate_instance(
        self, instance: _ModelT, import_validation_errors: dict[str, Any] | None = None, validate_unique: bool = True
    ) -> None: ...
    # For all the definitions below (from `save_instance()` to `import_row()`), `**kwargs` should contain:
    # dry_run: bool, use_transactions: bool, row_number: int, retain_instance_in_row_result: bool.
    # Users are free to pass extra arguments in `import_data()`so PEP 728 can probably be leveraged here.
    def save_instance(self, instance: _ModelT, is_create: bool, row: dict[str, Any], **kwargs: Any) -> None: ...
    def do_instance_save(self, instance: _ModelT) -> None: ...
    def before_save_instance(self, instance: _ModelT, row: dict[str, Any], **kwargs: Any) -> None: ...
    def after_save_instance(self, instance: _ModelT, row: dict[str, Any], **kwargs: Any) -> None: ...
    def delete_instance(self, instance: _ModelT, row: dict[str, Any], **kwargs: Any) -> None: ...
    def before_delete_instance(self, instance: _ModelT, row: dict[str, Any], **kwargs: Any) -> None: ...
    def after_delete_instance(self, instance: _ModelT, row: dict[str, Any], **kwargs: Any) -> None: ...
    def import_field(self, field: Field, instance: _ModelT, row: dict[str, Any], is_m2m: bool = False, **kwargs: Any) -> None: ...
    def get_import_fields(self) -> list[Field]: ...
    def import_instance(self, instance: _ModelT, row: dict[str, Any], **kwargs: Any) -> None: ...
    def save_m2m(self, instance: _ModelT, row: dict[str, Any], **kwargs: Any) -> None: ...
    def for_delete(self, row: dict[str, Any], instance: _ModelT) -> bool: ...
    def skip_row(
        self, instance: _ModelT, original: _ModelT, row: dict[str, Any], import_validation_errors: dict[str, Any] | None = None
    ) -> bool: ...
    def get_diff_headers(self) -> list[str]: ...
    def before_import(self, dataset: Dataset, **kwargs: Any) -> None: ...
    def after_import(self, dataset: Dataset, result: Result, **kwargs: Any) -> None: ...
    def before_import_row(self, row: dict[str, Any], **kwargs: Any) -> None: ...
    def after_import_row(self, row: dict[str, Any], row_result: RowResult, **kwargs: Any) -> None: ...
    def after_init_instance(self, instance: _ModelT, new: bool, row: dict[str, Any], **kwargs: Any) -> None: ...

    @overload
    def handle_import_error(self, result: Result, error: Exception, raise_errors: Literal[True]) -> NoReturn: ...
    @overload
    def handle_import_error(self, result: Result, error: Exception, raise_errors: Literal[False] = False) -> None: ...

    def import_row(self, row: dict[str, Any], instance_loader: BaseInstanceLoader, **kwargs: Any) -> RowResult: ...
    def import_data(
        self,
        dataset: Dataset,
        dry_run: bool = False,
        raise_errors: bool = False,
        use_transactions: bool | None = None,
        collect_failed_rows: bool = False,
        rollback_on_validation_errors: bool = False,
        **kwargs: Any,
    ) -> Result: ...
    def import_data_inner(
        self,
        dataset: Dataset,
        dry_run: bool,
        raise_errors: bool,
        using_transactions: bool,
        collect_failed_rows: bool,
        **kwargs: Any,
    ) -> Result: ...
    def get_import_order(self) -> tuple[str, ...]: ...
    def get_export_order(self) -> tuple[str, ...]: ...
    def before_export(self, queryset: QuerySet[_ModelT], **kwargs: Any) -> None: ...
    def after_export(self, queryset: QuerySet[_ModelT], dataset: Dataset, **kwargs: Any) -> None: ...
    def filter_export(self, queryset: QuerySet[_ModelT], **kwargs: Any) -> QuerySet[_ModelT]: ...
    def export_field(self, field: Field, instance: _ModelT, **kwargs: Any) -> str: ...
    def get_export_fields(self, selected_fields: Sequence[str] | None = None) -> list[Field]: ...
    def export_resource(self, instance: _ModelT, selected_fields: Sequence[str] | None = None, **kwargs: Any) -> list[str]: ...
    def get_export_headers(self, selected_fields: Sequence[str] | None = None) -> list[str]: ...
    def get_user_visible_headers(self) -> list[str]: ...
    def get_user_visible_fields(self) -> list[str]: ...
    def iter_queryset(self, queryset: QuerySet[_ModelT]) -> Iterator[_ModelT]: ...
    def export(self, queryset: QuerySet[_ModelT] | None = None, **kwargs: Any) -> Dataset: ...

class ModelResource(Resource[_ModelT], metaclass=ModelDeclarativeMetaclass):
    DEFAULT_RESOURCE_FIELD: ClassVar[type[Field]] = ...
    WIDGETS_MAP: ClassVar[dict[str, type[Widget]]]
    @classmethod
    def get_m2m_widget(cls, field: DjangoField[Any, Any]) -> partial[ManyToManyWidget[Any]]: ...
    @classmethod
    def get_fk_widget(cls, field: DjangoField[Any, Any]) -> partial[ForeignKeyWidget[Any]]: ...
    @classmethod
    def widget_from_django_field(cls, f: DjangoField[Any, Any], default: type[Widget] = ...) -> type[Widget]: ...
    @classmethod
    def widget_kwargs_for_field(cls, field_name: str, django_field: DjangoField[Any, Any]) -> dict[str, Any]: ...
    @classmethod
    def field_from_django_field(cls, field_name: str, django_field: DjangoField[Any, Any], readonly: bool) -> Field: ...
    def get_queryset(self) -> QuerySet[_ModelT]: ...
    def init_instance(self, row: dict[str, Any] | None = None) -> _ModelT: ...
    def after_import(self, dataset: Dataset, result: Result, **kwargs: Any) -> None: ...
    @classmethod
    def get_display_name(cls) -> str: ...

_ResourceT = TypeVar("_ResourceT", bound=Resource[Any])

# HK Type Vars could help type the first overload:
@overload
def modelresource_factory(model: Model, resource_class: type[_ResourceT]) -> _ResourceT: ...
@overload
def modelresource_factory(model: _ModelT) -> ModelResource[_ModelT]: ...
