import hashlib
import secrets
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any

from .....utils import DictUtils, Crypto, get_object_model_class, init_class_kwargs
from ....base.firebase_collection import FirebaseCollectionBase, getTimestamp
from .login_session_fields import LoginSessionFields, LoginSessionFieldsProps, STANDARD_FIELDS, LOGIN_SESSION_COLLECTION
from .....utils.response import ApiResponse, Error, ResponseStatus, StatusCode


@dataclass(init=False)
class LoginSession(FirebaseCollectionBase):
    id: str
    user_uid: str
    session_token: str
    device_name: str
    device_type: str
    ip_address: str
    user_agent: str
    is_active: bool
    mfa_verified: bool
    created_at: str
    last_seen_at: str
    revoked_at: str

    def __init__(self, session=None, **kwargs):
        if type(session) is str:
            session = {"id": session}
        (cls_object, keys, data_args) = init_class_kwargs(self, session, STANDARD_FIELDS, LoginSessionFieldsProps, LOGIN_SESSION_COLLECTION, ['id'], **kwargs)
        super().__init__(**data_args)
        for key in keys:
            setattr(self, key, cls_object[key])

    @staticmethod
    def generate_model(_key_="default_value"):
        model = {}
        props = LoginSessionFieldsProps
        for k in DictUtils.get_keys(props):
            model[k] = props[k][_key_]
        return model

    @staticmethod
    def from_dict(session: Any = None, db=None, collection_name=None) -> 'LoginSession':
        cls_object, keys = get_object_model_class(session, LoginSession, LoginSessionFieldsProps)
        return LoginSession(cls_object, db=db, collection_name=collection_name)

    def query(self, query_params: list, first=False, limit=None):
        try:
            result = []
            query_result = self.custom_query(query_params, first=first, limit=limit)
            if bool(query_result):
                if first:
                    return LoginSession.from_dict(session=query_result, db=self.db, collection_name=self.collection_name)
                else:
                    for doc in query_result:
                        result.append(LoginSession.from_dict(session=doc, db=self.db, collection_name=self.collection_name))
                    return result
            return None if first else []
        except Exception as e:
            print('Exception while querying LoginSession', e)
            return None if first else []

    def to_dict(self):
        return {
            "id": self.id,
            "user_uid": self.user_uid,
            "session_token": self.session_token,
            "device_name": self.device_name,
            "device_type": self.device_type,
            "ip_address": self.ip_address,
            "user_agent": self.user_agent,
            "is_active": self.is_active,
            "mfa_verified": self.mfa_verified,
            "created_at": self.created_at,
            "last_seen_at": self.last_seen_at,
            "revoked_at": self.revoked_at,
        }

    def to_json(self, _fields=None):
        return self.to_dict()

    def get_by_token(self, raw_token: str) -> 'LoginSession':
        hashed = hashlib.sha256(raw_token.encode()).hexdigest()
        return self.query([{"key": LoginSessionFields.session_token, "comp": "==", "value": hashed}], first=True)

    def list_by_user(self, uid: str):
        return self.query([
            {"key": LoginSessionFields.user_uid, "comp": "==", "value": uid},
            {"key": LoginSessionFields.is_active, "comp": "==", "value": True}
        ], first=False)

    def revoke(self, session_id: str):
        now = datetime.now(timezone.utc).isoformat()
        doc_ref = self.document_reference(session_id)
        doc_ref.set({"is_active": False, "revoked_at": now}, merge=True)

    def revoke_all(self, uid: str):
        sessions = self.list_by_user(uid)
        now = datetime.now(timezone.utc).isoformat()
        for s in sessions:
            self.document_reference(s.id).set({"is_active": False, "revoked_at": now}, merge=True)

    def touch(self, session_id: str):
        now = datetime.now(timezone.utc).isoformat()
        self.document_reference(session_id).set({"last_seen_at": now}, merge=True)

    def create(self, user_uid: str, ip_address: str = "", user_agent: str = "",
               device_name: str = "", device_type: str = "", mfa_verified: bool = False) -> tuple:
        raw_token = secrets.token_hex(32)
        hashed_token = hashlib.sha256(raw_token.encode()).hexdigest()
        now = datetime.now(timezone.utc).isoformat()
        model = {
            "user_uid": user_uid,
            "session_token": hashed_token,
            "device_name": device_name,
            "device_type": device_type,
            "ip_address": ip_address,
            "user_agent": user_agent,
            "is_active": True,
            "mfa_verified": mfa_verified,
            "created_at": now,
            "last_seen_at": now,
            "revoked_at": "",
        }
        obj = LoginSession.from_dict(model)
        obj.id = Crypto().generate_id(user_uid + "~session~" + raw_token[:8])
        doc_ref = LoginSession(obj.to_json()).document_reference()
        doc_ref.set({**obj.to_json(), "createdAt": getTimestamp()}, merge=True)
        return obj, raw_token
