import uuid
import secrets
import hashlib
import requests

from sqlalchemy import or_
from datetime import timedelta
from fastapi.exceptions import HTTPException
from fastapi import status

from databases.no_sql_models.validations import (
    PasswordHistoryViaUser,
    AccessHistoryViaUser,
)

from api_library.date_time_actions.date_functions import system_arrow, client_arrow
from api_configs import ApiStatic, Auth


class PasswordModule:

    @classmethod
    def generate_token(cls, length):
        return secrets.token_urlsafe(length)

    @classmethod
    def create_hashed_password(cls, domain, id_, password):
        salted_password = f"{domain}-{id_}-{password}"
        return hashlib.sha256(salted_password.encode()).hexdigest()

    @classmethod
    def check_hashed_password(cls, domain, id_, password, password_hashed):
        if not password_hashed:
            raise HTTPException(
                status_code=401,
                detail="Password is not changed yet user has no password.",
            )
        return cls.create_hashed_password(domain, id_, password) == password_hashed


class AuthModule(PasswordModule):

    @classmethod
    def check_user_exits(cls, access_key, domain):
        from databases import Users

        found_user = Users.query.filter(
            or_(
                Users.email == str(access_key).lower(),
                Users.phone_number == str(access_key).replace(" ", ""),
            ),
        ).first()
        if not found_user:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Given access key or domain is not matching with the any user record.",
            )

        other_domains_list = found_user.get_main_domain_and_other_domains(
            get_main_domain=False
        )
        if domain not in other_domains_list:
            raise HTTPException(
                status_code=401,
                detail=dict(message="Unauthorized User attempts to connect api"),
            )
        return found_user

    def generate_access_token(self):
        return self.generate_token(Auth.ACCESS_TOKEN_LENGTH)

    def remove_refresher_token(self, domain, disconnect: bool = False):
        from databases import (
            UsersTokens,
        )

        if disconnect:
            registered_tokens = UsersTokens.filter_all(
                UsersTokens.user_id == self.id, system=True
            )
        else:
            registered_tokens = UsersTokens.filter_all(
                UsersTokens.domain == domain,
                UsersTokens.user_id == self.id,
                system=True,
            )
        registered_tokens.query.delete()
        UsersTokens.save()

    def check_password(self, password):
        main_domain = self.get_main_domain_and_other_domains(get_main_domain=True)
        if check_password := self.check_hashed_password(
            domain=main_domain,
            id_=str(self.uu_id),
            password_hashed=self.hash_password,
            password=password,
        ):
            return check_password
        raise HTTPException(
            status_code=401,
            detail="Password is not correct.",
        )

    def check_password_is_different(self, password):
        main_domain = self.get_main_domain_and_other_domains(get_main_domain=True)
        if self.hash_password == self.create_hashed_password(
            domain=main_domain, id_=self.uu_id, password=password
        ):
            raise HTTPException(
                status_code=401,
                detail="New password is same with old password.",
            )

    @staticmethod
    def create_password(found_user, password, password_token=None):
        from databases import MongoQueryIdentity

        if found_user.password_token:
            replace_day = 0
            try:
                replace_day = int(
                    str(found_user.password_expires_day or 0)
                    .split(",")[0]
                    .replace(" days", "")
                )
            except Exception as e:
                err = e
            token_is_expired = system_arrow.now() >= system_arrow.get(
                str(found_user.password_expiry_begins)
            ).shift(days=replace_day)

            if not password_token == found_user.password_token and token_is_expired:
                raise HTTPException(
                    status_code=401,
                    detail="Password token is not valid. Please request a new password token.",
                )
        query_engine = MongoQueryIdentity(company_uuid=found_user.related_company)

        domain_via_user = query_engine.get_domain_via_user(
            user_uu_id=str(found_user.uu_id)
        )["main_domain"]
        new_password_dict = {
            "password": found_user.create_hashed_password(
                domain=domain_via_user, id_=str(found_user.uu_id), password=password
            ),
            "date": str(system_arrow.now().date()),
        }
        history_dict = PasswordHistoryViaUser(
            user_uu_id=str(found_user.uu_id),
            password_add=new_password_dict,
            access_history_detail={
                "request": "",
                "ip": "",
            },
        )
        found_user.password_expiry_begins = str(system_arrow.now())
        found_user.hash_password = new_password_dict.get("password")
        found_user.password_token = "" if found_user.password_token else ""
        query_engine.refresh_password_history_via_user(payload=history_dict)
        found_user.save()
        return found_user

    @staticmethod
    def reset_password_token(found_user):
        found_user.password_expiry_begins = str(system_arrow.now())
        found_user.password_token = found_user.generate_token(
            Auth.REFRESHER_TOKEN_LENGTH
        )
        found_user.save()
        return found_user.password_token

    def generate_refresher_token(self, domain: str, remember_me=False):
        from databases import (
            UsersTokens,
        )

        if remember_me:
            refresh_token = self.generate_token(Auth.REFRESHER_TOKEN_LENGTH)
            if already_token := UsersTokens.filter_by_one(
                system=True, user_id=self.id, token_type="RememberMe", domain=domain
            ).data:
                already_token.update(token=refresh_token)
                already_token.expires_at = system_arrow.shift(days=3)
                already_token.save()
                return refresh_token
            users_tokens = UsersTokens.filter_by_all(
                user_id=self.id, token_type="RememberMe", domain=domain, system=True
            ).data
            if users_tokens:
                users_tokens.query.delete()
                UsersTokens.save()

            users_token = UsersTokens.find_or_create(
                user_id=self.id,
                token_type="RememberMe",
                token=refresh_token,
                domain=domain,
            )
            users_token.save_and_confirm()
            return refresh_token
        return None

    def remainder_day(self):
        join_list = [
            _ for _ in str(self.password_expires_day).split(",")[0] if _.isdigit()
        ]
        return float(timedelta(days=int("".join(join_list))).seconds)


class UserLoginModule(AuthModule):

    @classmethod
    def login_user_with_credentials(cls, data, request):
        from api_services.redis.auth_actions.auth import save_access_token_to_redis
        from databases import (
            Users,
            People,
            MongoQueryIdentity,
        )

        found_user = Users.check_user_exits(
            access_key=data.access_key, domain=data.domain
        )
        access_token = found_user.generate_access_token()
        query_engine = MongoQueryIdentity(company_uuid=found_user.related_company)
        if found_user.check_password(password=data.password):
            access_object_to_redis = save_access_token_to_redis(
                request=request,
                found_user=found_user,
                domain=data.domain,
                access_token=access_token,
            )
            refresher_token = found_user.generate_refresher_token(
                domain=data.domain, remember_me=data.remember_me
            )
            headers_request = request.headers
            headers_request = dict(headers_request)
            headers_request["evyos-user-agent"] = headers_request.get("user-agent")
            headers_request["evyos-platform"] = headers_request.get("user-agent")
            headers_request["evyos-ip-ext"] = "94.54.68.158"
            found_user.last_agent = headers_request.get("evyos-user-agent", None)
            found_user.last_platform = headers_request.get("evyos-platform", None)
            found_user.last_remote_addr = headers_request.get("evyos-ip-ext", None)
            found_user.last_seen = str(system_arrow.now())
            if ext_ip := headers_request.get("evyos-ip-ext"):
                agent = headers_request.get("evyos-user-agent", "")
                platform = headers_request.get("evyos-platform", "")
                address = requests.get(f"http://ip-api.com/json/{ext_ip}").json()
                address_package = {
                    "city": address["city"],
                    "zip": address["zip"],
                    "country": address["country"],
                    "countryCode": address["countryCode"],
                    "region": address["region"],
                    "regionName": address["regionName"],
                }
                mongo_db = MongoQueryIdentity(
                    company_uuid=str(found_user.related_company).replace(" ", ""),
                    storage_reasoning="AccessHistory",
                )
                filter_query = {
                    "agent": agent,
                    "platform": platform,
                    "address": address_package,
                    "user_id": found_user.id,
                }
                already_exits = mongo_db.mongo_engine.filter_by(filter_query) or None
                no_address_validates = mongo_db.mongo_engine.get_all()[0] == 0
                record_id = uuid.uuid4().__str__()
                notice_link = ApiStatic.blacklist_login(record_id=record_id)
                found_people = People.filter_one(People.id == found_user.person_id).data
                access_via_user = query_engine.update_access_history_via_user(
                    AccessHistoryViaUser(
                        **{
                            "user_uu_id": found_user.uu_id.__str__(),
                            "access_history": {
                                "record_id": record_id,
                                "agent": agent,
                                "platform": platform,
                                "address": address_package,
                                "ip": ext_ip,
                                "access_token": access_token,
                                "created_at": system_arrow.now().timestamp(),
                                # "is_confirmed": True if no_address_validates else False,
                                # "is_first": True if no_address_validates else False,
                            },
                        }
                    )
                )
                if already_exits:
                    update_mongo = mongo_db.mongo_engine.table.update_one(
                        filter=filter_query,
                        update={
                            "$set": {
                                "ip": ext_ip,
                                "access_token": access_token,
                                "created_at": system_arrow.now().timestamp(),
                            }
                        },
                    )
                else:
                    mongo_db.mongo_engine.insert(
                        payload={
                            "user_id": found_user.id,
                            "record_id": record_id,
                            "agent": agent,
                            "platform": platform,
                            "address": address_package,
                            "ip": ext_ip,
                            "access_token": access_token,
                            "created_at": system_arrow.now().timestamp(),
                            "is_confirmed": True if no_address_validates else False,
                            "is_first": True if no_address_validates else False,
                        }
                    )
            found_user.remember_me = bool(data.remember_me)
            found_user.save()
            return {
                "access_token": access_token,
                "refresher_token": refresher_token,
                "user": found_user,
                "access_object": access_object_to_redis,
            }
        raise HTTPException(
            status_code=401,
            detail="Login is not successful. Please check your credentials.",
        )


# UserLogger.log_error(
#     dict(
#         user_id=found_user.id,
#         domain=data.domain,
#         access_key=data.access_key,
#         agent=found_user.last_agent,
#         ip=getattr(request, "remote_addr", None)
#         or request.headers.get("X-Forwarded-For", None),
#         platform=found_user.last_platform,
#         login_date=str(DateTimeLocal.now()),
#         is_login=True,
#     )
# )

# if (
#     not str(found_people.country_code).lower()
#     == str(address_package.get("countryCode")).lower()
# ):
#     send_email_completed = send_email(
#         subject=f"Dear {found_user.nick_name}, your password has been changed.",
#         receivers=[str(found_user.email)],
#         html=invalid_ip_or_address_found(
#             user_name=found_user.nick_name,
#             address=address_package,
#             notice_link=notice_link,
#         ),
#     )
#     if not send_email_completed:
#         raise HTTPException(
#             status_code=400,
#             detail="An error occured at sending email. Please contact with support team.",
#         )
