from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type

from beanie.odm.fields import LinkInfo, LinkTypes

if TYPE_CHECKING:
    from beanie import Document


# TODO: check if this is the most efficient way for
#  appending subqueries to the queries var


def construct_lookup_queries(
    cls: Type["Document"],
    nesting_depth: Optional[int] = None,
    nesting_depths_per_field: Optional[Dict[str, int]] = None,
) -> List[Dict[str, Any]]:
    queries: List = []
    link_fields = cls.get_link_fields()
    if link_fields is not None:
        for link_info in link_fields.values():
            final_nesting_depth = (
                nesting_depths_per_field.get(link_info.field_name, None)
                if nesting_depths_per_field is not None
                else None
            )
            if final_nesting_depth is None:
                final_nesting_depth = nesting_depth
            construct_query(
                link_info=link_info,
                queries=queries,
                database_major_version=cls._database_major_version,
                current_depth=final_nesting_depth,
            )
    return queries


def construct_query(
    link_info: LinkInfo,
    queries: List,
    database_major_version: int,
    current_depth: Optional[int] = None,
):
    if link_info.is_fetchable is False or (
        current_depth is not None and current_depth <= 0
    ):
        return
    if link_info.link_type in [
        LinkTypes.DIRECT,
        LinkTypes.OPTIONAL_DIRECT,
    ]:
        if database_major_version >= 5 or link_info.nested_links is None:
            lookup_steps = [
                {
                    "$lookup": {
                        "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                        "localField": f"{link_info.lookup_field_name}.$id",
                        "foreignField": "_id",
                        "as": f"_link_{link_info.field_name}",
                    }
                },
                {
                    "$unwind": {
                        "path": f"$_link_{link_info.field_name}",
                        "preserveNullAndEmptyArrays": True,
                    }
                },
                {
                    "$addFields": {
                        link_info.field_name: {
                            "$cond": {
                                "if": {
                                    "$ifNull": [
                                        f"$_link_{link_info.field_name}",
                                        False,
                                    ]
                                },
                                "then": f"$_link_{link_info.field_name}",
                                "else": f"${link_info.field_name}",
                            }
                        }
                    }
                },
                {"$project": {f"_link_{link_info.field_name}": 0}},
            ]  # type: ignore
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            if link_info.nested_links is not None:
                lookup_steps[0]["$lookup"]["pipeline"] = []  # type: ignore
                for nested_link in link_info.nested_links:
                    construct_query(
                        link_info=link_info.nested_links[nested_link],
                        queries=lookup_steps[0]["$lookup"]["pipeline"],  # type: ignore
                        database_major_version=database_major_version,
                        current_depth=new_depth,
                    )
            queries += lookup_steps

        else:
            lookup_steps = [
                {
                    "$lookup": {
                        "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                        "let": {
                            "link_id": f"${link_info.lookup_field_name}.$id"
                        },
                        "as": f"_link_{link_info.field_name}",
                        "pipeline": [
                            {
                                "$match": {
                                    "$expr": {"$eq": ["$_id", "$$link_id"]}
                                }
                            },
                        ],
                    }
                },
                {
                    "$unwind": {
                        "path": f"$_link_{link_info.field_name}",
                        "preserveNullAndEmptyArrays": True,
                    }
                },
                {
                    "$addFields": {
                        link_info.field_name: {
                            "$cond": {
                                "if": {
                                    "$ifNull": [
                                        f"$_link_{link_info.field_name}",
                                        False,
                                    ]
                                },
                                "then": f"$_link_{link_info.field_name}",
                                "else": f"${link_info.field_name}",
                            }
                        }
                    }
                },
                {"$project": {f"_link_{link_info.field_name}": 0}},
            ]
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            for nested_link in link_info.nested_links:
                construct_query(
                    link_info=link_info.nested_links[nested_link],
                    queries=lookup_steps[0]["$lookup"]["pipeline"],  # type: ignore
                    database_major_version=database_major_version,
                    current_depth=new_depth,
                )
            queries += lookup_steps

    elif link_info.link_type in [
        LinkTypes.BACK_DIRECT,
        LinkTypes.OPTIONAL_BACK_DIRECT,
    ]:
        if database_major_version >= 5 or link_info.nested_links is None:
            lookup_steps = [
                {
                    "$lookup": {
                        "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                        "localField": "_id",
                        "foreignField": f"{link_info.lookup_field_name}.$id",
                        "as": f"_link_{link_info.field_name}",
                    }
                },
                {
                    "$unwind": {
                        "path": f"$_link_{link_info.field_name}",
                        "preserveNullAndEmptyArrays": True,
                    }
                },
                {
                    "$addFields": {
                        link_info.field_name: {
                            "$cond": {
                                "if": {
                                    "$ifNull": [
                                        f"$_link_{link_info.field_name}",
                                        False,
                                    ]
                                },
                                "then": f"$_link_{link_info.field_name}",
                                "else": f"${link_info.field_name}",
                            }
                        }
                    }
                },
                {"$project": {f"_link_{link_info.field_name}": 0}},
            ]  # type: ignore
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            if link_info.nested_links is not None:
                lookup_steps[0]["$lookup"]["pipeline"] = []  # type: ignore
                for nested_link in link_info.nested_links:
                    construct_query(
                        link_info=link_info.nested_links[nested_link],
                        queries=lookup_steps[0]["$lookup"]["pipeline"],  # type: ignore
                        database_major_version=database_major_version,
                        current_depth=new_depth,
                    )
            queries += lookup_steps

        else:
            lookup_steps = [
                {
                    "$lookup": {
                        "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                        "let": {"link_id": "$_id"},
                        "as": f"_link_{link_info.field_name}",
                        "pipeline": [
                            {
                                "$match": {
                                    "$expr": {
                                        "$eq": [
                                            f"${link_info.lookup_field_name}.$id",
                                            "$$link_id",
                                        ]
                                    }
                                }
                            },
                        ],
                    }
                },
                {
                    "$unwind": {
                        "path": f"$_link_{link_info.field_name}",
                        "preserveNullAndEmptyArrays": True,
                    }
                },
                {
                    "$addFields": {
                        link_info.field_name: {
                            "$cond": {
                                "if": {
                                    "$ifNull": [
                                        f"$_link_{link_info.field_name}",
                                        False,
                                    ]
                                },
                                "then": f"$_link_{link_info.field_name}",
                                "else": f"${link_info.field_name}",
                            }
                        }
                    }
                },
                {"$project": {f"_link_{link_info.field_name}": 0}},
            ]
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            for nested_link in link_info.nested_links:
                construct_query(
                    link_info=link_info.nested_links[nested_link],
                    queries=lookup_steps[0]["$lookup"]["pipeline"],  # type: ignore
                    database_major_version=database_major_version,
                    current_depth=new_depth,
                )
            queries += lookup_steps

    elif link_info.link_type in [
        LinkTypes.LIST,
        LinkTypes.OPTIONAL_LIST,
    ]:
        if database_major_version >= 5 or link_info.nested_links is None:
            queries.append(
                {
                    "$lookup": {
                        "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                        "localField": f"{link_info.lookup_field_name}.$id",
                        "foreignField": "_id",
                        "as": link_info.field_name,
                    }
                }
            )
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            if link_info.nested_links is not None:
                queries[-1]["$lookup"]["pipeline"] = []
                for nested_link in link_info.nested_links:
                    construct_query(
                        link_info=link_info.nested_links[nested_link],
                        queries=queries[-1]["$lookup"]["pipeline"],
                        database_major_version=database_major_version,
                        current_depth=new_depth,
                    )
        else:
            lookup_step = {
                "$lookup": {
                    "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                    "let": {"link_id": f"${link_info.lookup_field_name}.$id"},
                    "as": link_info.field_name,
                    "pipeline": [
                        {"$match": {"$expr": {"$in": ["$_id", "$$link_id"]}}},
                    ],
                }
            }
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            for nested_link in link_info.nested_links:
                construct_query(
                    link_info=link_info.nested_links[nested_link],
                    queries=lookup_step["$lookup"]["pipeline"],
                    database_major_version=database_major_version,
                    current_depth=new_depth,
                )
            queries.append(lookup_step)

    elif link_info.link_type in [
        LinkTypes.BACK_LIST,
        LinkTypes.OPTIONAL_BACK_LIST,
    ]:
        if database_major_version >= 5 or link_info.nested_links is None:
            queries.append(
                {
                    "$lookup": {
                        "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                        "localField": "_id",
                        "foreignField": f"{link_info.lookup_field_name}.$id",
                        "as": link_info.field_name,
                    }
                }
            )
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            if link_info.nested_links is not None:
                queries[-1]["$lookup"]["pipeline"] = []
                for nested_link in link_info.nested_links:
                    construct_query(
                        link_info=link_info.nested_links[nested_link],
                        queries=queries[-1]["$lookup"]["pipeline"],
                        database_major_version=database_major_version,
                        current_depth=new_depth,
                    )
        else:
            lookup_step = {
                "$lookup": {
                    "from": link_info.document_class.get_pymongo_collection().name,  # type: ignore
                    "let": {"link_id": "$_id"},
                    "as": link_info.field_name,
                    "pipeline": [
                        {
                            "$match": {
                                "$expr": {
                                    "$in": [
                                        "$$link_id",
                                        f"${link_info.lookup_field_name}.$id",
                                    ]
                                }
                            }
                        }
                    ],
                }
            }
            new_depth = (
                current_depth - 1 if current_depth is not None else None
            )
            for nested_link in link_info.nested_links:
                construct_query(
                    link_info=link_info.nested_links[nested_link],
                    queries=lookup_step["$lookup"]["pipeline"],
                    database_major_version=database_major_version,
                    current_depth=new_depth,
                )
            queries.append(lookup_step)

    return queries


def split_text_query(
    query: Dict[str, Any],
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
    """Divide query into text and non-text matches

    :param query: Dict[str, Any] - query dict
    :return: Tuple[Dict[str, Any], Dict[str, Any]] - text and non-text queries,
        respectively
    """

    root_text_query_args: Dict[str, Any] = query.get("$text", None)
    root_non_text_queries: Dict[str, Any] = {
        k: v for k, v in query.items() if k not in {"$text", "$and"}
    }

    text_queries: List[Dict[str, Any]] = (
        [{"$text": root_text_query_args}] if root_text_query_args else []
    )
    non_text_queries: List[Dict[str, Any]] = (
        [root_non_text_queries] if root_non_text_queries else []
    )

    for match_case in query.get("$and", []):
        if "$text" in match_case:
            text_queries.append(match_case)
        else:
            non_text_queries.append(match_case)

    return text_queries, non_text_queries
