from itertools import count

import pymongo

from json import loads
from bson import ObjectId, json_util
from pydantic import BaseModel
from api_configs import MongoConfig

from pymongo import MongoClient
from pymongo.collection import Collection
from pymongo.results import InsertManyResult

# from configs import TestMongo as MongoConfig


class Paginate(BaseModel):
    pageSize: int = 10
    pageNumber: int = 1
    sortField: str = "_id"
    sortOrder: str = "desc"

    def grab_paginates(self):
        size_ = self.pageSize
        return (
            size_,
            size_ * (self.pageNumber - 1),
            self.sortField,
            -1 if self.sortOrder == "desc" else 1,
        )


class MongoResponse:

    def __init__(
        self,
        status: bool,
        message: str,
        message_code: str = None,
        data: dict = None,
        mongo=None,
        count: int = 1,
    ):
        self.status = status
        self.message = message
        self.data = data
        self.mongo = mongo
        self.message_code = message_code
        self.count = count

    def as_dict(self):
        return {
            "status": self.status,
            "count": self.count,
            "message": self.message,
            "message_code": self.message_code,
            "data": self.data,
        }


class MongoQuery:

    def __init__(self, table_name: str, database_name: str):
        database = MongoClient(MongoConfig.url)[database_name]
        self.table: Collection = database[table_name]

    @staticmethod
    def grab_paginates(paginate):
        return (
            paginate.size,
            paginate.size * (paginate.page - 1),
            paginate.order_field,
            -1 if paginate.order_type == "desc" else 1,
        )

    @staticmethod
    def parse_json(data):
        return loads(json_util.dumps(data))

    def insert(self, payload) -> MongoResponse:
        insert = self.table.insert_one(document=payload)
        if insert.acknowledged:
            return MongoResponse(
                status=True,
                message="The document has been created",
                message_code="CREATED",
                data=payload,
                mongo=insert,
            )
        return MongoResponse(
            status=False,
            message="The document has not been created",
            message_code="NOT_CREATED",
            data=None,
            mongo=insert,
            count=0,
        )

    def insert_many(self, payload) -> MongoResponse:
        insert_many = self.table.insert_many(documents=payload)
        if insert_many.acknowledged:
            return MongoResponse(
                status=True,
                message="The documents have been created",
                message_code="CREATED",
                data=payload,
                mongo=insert_many,
                count=len(payload),
            )
        return MongoResponse(
            status=False,
            message="The documents have not been created",
            message_code="NOT_CREATED",
            data=None,
            mongo=insert_many,
            count=0,
        )

    def find_or_insert(self, payload, field: str = "id") -> MongoResponse:
        if field == "id":
            found_row = self.get_one(match=payload["_id"], field="_id")
            if found_row.status:
                return MongoResponse(
                    status=False,
                    message="The document already exists",
                    data=found_row.data,
                    mongo=found_row.mongo,
                )
            if mongo := self.table.insert_one(document=payload).acknowledged:
                return MongoResponse(
                    status=True,
                    message="The document has been created",
                    data=payload,
                    mongo=mongo,
                )

        found_row = self.get_one(match=payload[field], field=field)
        if found_row.status:
            return MongoResponse(
                status=False,
                message_code="ALREADY_EXISTS",
                message="The document already exists",
                data=found_row.data,
                mongo=found_row.mongo,
            )
        insert_row = self.table.insert_one(document=payload)
        return MongoResponse(
            status=True,
            message="The document has been created",
            message_code="CREATED",
            data=payload,
            mongo=insert_row,
        )

    def update(self, match, payload, field: str = "id"):
        if field == "id":
            filter_ = {"_id": ObjectId(match)}
            update_one = self.table.update_one(filter=filter_, update={"$set": payload})
            return MongoResponse(
                status=True,
                message="The document has been updated",
                message_code="UPDATED",
                data=self.get_one(match=match, field=field).data,
                mongo=update_one,
            )
        update_one = self.table.update_one(
            filter={field: match}, update={"$set": payload}
        )
        return MongoResponse(
            status=True,
            message="The document has been updated",
            message_code="UPDATED",
            data=self.get_one(match=match, field=field).data,
            mongo=update_one,
        )

    def get_one(self, match, field: str = "id"):
        if field == "id":
            if get_one := self.table.find_one(filter={"_id": ObjectId(match)}):
                return MongoResponse(
                    status=True,
                    message="The document has been found",
                    message_code="FOUND",
                    data=self.parse_json(data=get_one),
                    mongo=get_one,
                )
        if get_one := self.table.find_one(filter={field: match}):
            return MongoResponse(
                status=True,
                message="The document has been found",
                message_code="FOUND",
                data=self.parse_json(data=get_one),
                mongo=get_one,
            )
        return MongoResponse(
            status=False,
            message="The document has not been found",
            message_code="NOT_FOUND",
            data={},
            mongo=None,
        )

    def filter_by(self, payload, sort_by: str = "_id", sort_direction: str = "asc"):
        sort_direction = (
            pymongo.ASCENDING
            if str(sort_direction).lower() == "asc"
            else pymongo.DESCENDING
        )
        return_ = self.table.find(payload).sort(sort_by, sort_direction)
        data = self.parse_json(data=return_)
        if len(data) == 0:
            return MongoResponse(
                status=False,
                message="The documents have not been found",
                message_code="NOT_FOUND",
                data=[],
                mongo=return_,
                count=0,
            )
        return MongoResponse(
            status=True,
            message="The documents have been found",
            message_code="FOUND",
            count=self.table.count_documents(payload),
            data=data,
        )

    def delete_one(self, match, field: str = "id"):
        if field == "id":
            delete_one = self.table.delete_one(filter={"_id": ObjectId(match)})
            if delete_one.deleted_count:
                return MongoResponse(
                    status=True,
                    message="The document has been deleted",
                    message_code="DELETED",
                    data=None,
                    count=delete_one.deleted_count,
                    mongo=delete_one,
                )
        delete_one = self.table.delete_one(filter={field: match})
        if delete_one.deleted_count:
            return MongoResponse(
                status=True,
                message="The document has been deleted",
                message_code="DELETED",
                data=None,
                count=delete_one.deleted_count,
                mongo=delete_one,
            )
        return MongoResponse(
            status=False,
            message="The document has not been deleted",
            message_code="NOT_DELETED",
            data=None,
            mongo=None,
            count=0,
        )

    def delete_all(self, match, field: str = "id"):
        if field == "id":
            delete_many = self.table.delete_many(filter={"_id": ObjectId(match)})
            if delete_many.deleted_count:
                return MongoResponse(
                    status=True,
                    message="The document has been deleted",
                    message_code="DELETED",
                    data=None,
                    count=delete_many.deleted_count,
                    mongo=delete_many,
                )
        delete_many = self.table.delete_many(filter={field: match})
        if delete_many.deleted_count:
            return MongoResponse(
                status=True,
                message="The document has been deleted",
                message_code="DELETED",
                data=None,
                count=delete_many.deleted_count,
                mongo=delete_many,
            )
        return MongoResponse(
            status=False,
            message="The document has not been deleted",
            message_code="NOT_DELETED",
            data=None,
            mongo=None,
            count=0,
        )

    def list_all(self, paginate: Paginate):
        size, skip, field, order = paginate.grab_paginates()
        return_ = self.table.find().sort([(field, order)]).skip(skip).limit(size)
        self.table.count_documents({}), self.parse_json(data=return_)
        data = self.parse_json(data=return_)
        if len(data) == 0:
            return MongoResponse(
                status=False,
                message="The documents have not been found",
                message_code="NOT_FOUND",
                data=None,
                mongo=return_,
                count=0,
            )
        return MongoResponse(
            status=True,
            message="The documents have been found",
            message_code="FOUND",
            count=self.table.count_documents({}),
            data=data,
            mongo=return_,
        )

    def get_all(self):
        return_ = self.table.find()
        return MongoResponse(
            status=True,
            message="The documents have been found",
            message_code="FOUND",
            count=self.table.count_documents({}),
            data=self.parse_json(data=return_),
            mongo=return_,
        )


# Mongo = MongoQuery(table_name="XcompanyConfig", database_name="mongo_database")
